unique-sdk 2026.32.0.dev9__tar.gz → 2026.32.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.
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/PKG-INFO +1 -1
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/pyproject.toml +1 -1
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/cli.py +25 -4
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/commands/elicitation.py +160 -25
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/shell.py +9 -9
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/skills/unique-cli-elicitation/SKILL.md +120 -33
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/README.md +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/__init__.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/_api_requestor.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/_api_resource.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/_api_version.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/_error.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/_http_client.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/_list_object.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/_object_classes.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/_request_options.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/_unique_object.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/_unique_ql.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/_unique_response.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/_util.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/_version.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/_webhook.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/__init__.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_acronyms.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_agentic_table.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_analytics_order.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_benchmarking.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_briefing.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_chat_completion.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_content.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_elicitation.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_embedding.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_event.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_folder.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_group.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_integrated.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_llm_models.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_mcp.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_message.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_message_assessment.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_message_execution.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_message_log.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_message_tool.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_module.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_scheduled_task.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_search.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_search_string.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_short_term_memory.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_space.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_user.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_web_search.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/__init__.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/__main__.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/commands/__init__.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/commands/_citation_manifest.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/commands/agentic_table.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/commands/browser.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/commands/cite_file.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/commands/files.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/commands/folders.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/commands/mcp.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/commands/navigation.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/commands/read.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/commands/scheduled_tasks.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/commands/search.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/commands/subagent.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/commands/web_search.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/commands/web_search_config.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/config.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/formatting.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/identity.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/metadata_filter.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/skills/unique-cli-agentic-table/SKILL.md +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/skills/unique-cli-file-management/SKILL.md +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/skills/unique-cli-mcp/SKILL.md +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/skills/unique-cli-scheduled-tasks/SKILL.md +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/skills/unique-cli-search/SKILL.md +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/skills/unique-cli-subagent/SKILL.md +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/skills/unique-cli-uploaded-search/SKILL.md +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/skills/unique-cli-web-search/SKILL.md +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/state.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/utils/analytics_order_run.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/utils/benchmarking_run.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/utils/chat_history.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/utils/chat_in_space.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/utils/file_io.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/utils/sources.py +0 -0
- {unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.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.32.0.
|
|
3
|
+
Version: 2026.32.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>
|
|
@@ -1154,9 +1154,24 @@ def elicit() -> None:
|
|
|
1154
1154
|
default=DEFAULT_WAIT_TIMEOUT_SECONDS,
|
|
1155
1155
|
show_default=True,
|
|
1156
1156
|
help=(
|
|
1157
|
-
"Max seconds to block waiting for the user's response.
|
|
1158
|
-
"sets when the elicitation expires, so the
|
|
1159
|
-
"when we stop waiting and the chat UI can
|
|
1157
|
+
"Max seconds to block waiting for the user's response. Without "
|
|
1158
|
+
"--expires-in, this also sets when the elicitation expires, so the "
|
|
1159
|
+
"request expires exactly when we stop waiting and the chat UI can "
|
|
1160
|
+
"offer a way to continue."
|
|
1161
|
+
),
|
|
1162
|
+
)
|
|
1163
|
+
@click.option(
|
|
1164
|
+
"--expires-in",
|
|
1165
|
+
"expires_in_seconds",
|
|
1166
|
+
type=int,
|
|
1167
|
+
default=None,
|
|
1168
|
+
help=(
|
|
1169
|
+
"Seconds before the platform expires the request, decoupled from "
|
|
1170
|
+
"--timeout (the local wait budget). Use this when this process's "
|
|
1171
|
+
"wait budget is shorter than how long a human should realistically "
|
|
1172
|
+
"get to answer (e.g. a harness with its own foreground timeout) -- "
|
|
1173
|
+
"otherwise the request expires under the user before they can "
|
|
1174
|
+
"answer. Defaults to --timeout, exactly matching prior behavior."
|
|
1160
1175
|
),
|
|
1161
1176
|
)
|
|
1162
1177
|
@click.option(
|
|
@@ -1217,6 +1232,7 @@ def elicit_ask(
|
|
|
1217
1232
|
chat_id: str | None,
|
|
1218
1233
|
message_id: str | None,
|
|
1219
1234
|
timeout: int,
|
|
1235
|
+
expires_in_seconds: int | None,
|
|
1220
1236
|
poll_interval: float,
|
|
1221
1237
|
metadata: tuple[str, ...],
|
|
1222
1238
|
visible: bool = True,
|
|
@@ -1229,7 +1245,9 @@ def elicit_ask(
|
|
|
1229
1245
|
\b
|
|
1230
1246
|
Creates a FORM elicitation in the Unique UI with the given MESSAGE
|
|
1231
1247
|
and blocks until the user responds, the elicitation is declined /
|
|
1232
|
-
cancelled / expired, or --timeout is reached.
|
|
1248
|
+
cancelled / expired, or --timeout is reached. Immediately after
|
|
1249
|
+
creation, before polling starts, a single line is written to stderr:
|
|
1250
|
+
"UNIQUE_ELICITATION_CREATED id=<id> expires_at=<iso8601>".
|
|
1233
1251
|
|
|
1234
1252
|
\b
|
|
1235
1253
|
Examples:
|
|
@@ -1237,6 +1255,8 @@ def elicit_ask(
|
|
|
1237
1255
|
unique-cli elicit ask "Confirm deletion of /Archive" --timeout 60
|
|
1238
1256
|
unique-cli elicit ask "Pick a region" \\
|
|
1239
1257
|
--schema '{"type":"object","properties":{"region":{"type":"string","enum":["EU","US","APAC"]}},"required":["region"]}'
|
|
1258
|
+
unique-cli elicit ask "Long-running approval" \\
|
|
1259
|
+
--expires-in 7200 --timeout 300
|
|
1240
1260
|
"""
|
|
1241
1261
|
parsed_metadata: list[tuple[str, str]] = []
|
|
1242
1262
|
for kv in metadata:
|
|
@@ -1253,6 +1273,7 @@ def elicit_ask(
|
|
|
1253
1273
|
"chat_id": chat_id,
|
|
1254
1274
|
"message_id": _resolve_cli_message_id(ctx, message_id),
|
|
1255
1275
|
"timeout": timeout,
|
|
1276
|
+
"expires_in_seconds": expires_in_seconds,
|
|
1256
1277
|
"poll_interval": poll_interval,
|
|
1257
1278
|
"metadata": parsed_metadata or None,
|
|
1258
1279
|
"visible": visible,
|
{unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/commands/elicitation.py
RENAMED
|
@@ -13,8 +13,9 @@ from __future__ import annotations
|
|
|
13
13
|
|
|
14
14
|
import json
|
|
15
15
|
import os
|
|
16
|
+
import sys
|
|
16
17
|
import time
|
|
17
|
-
from datetime import datetime, timezone
|
|
18
|
+
from datetime import datetime, timedelta, timezone
|
|
18
19
|
from typing import Any, Literal, cast
|
|
19
20
|
|
|
20
21
|
import unique_sdk
|
|
@@ -37,6 +38,23 @@ TERMINAL_STATUSES = {
|
|
|
37
38
|
"COMPLETED",
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
# --- Transient-failure retry (UN-23310) ---------------------------------
|
|
42
|
+
#
|
|
43
|
+
# ``cmd_elicit_wait`` used to return on the very first ``APIError`` /
|
|
44
|
+
# ``APIConnectionError`` raised mid-poll, treating a transient 502 the same
|
|
45
|
+
# as a genuine terminal condition. Now that a single ``elicit ask`` call is
|
|
46
|
+
# expected to carry the whole wait unsupervised, that one dropped connection
|
|
47
|
+
# would otherwise cost the entire turn. These constants bound the retry
|
|
48
|
+
# loop's backoff; the overall ``--timeout`` remains the hard outer bound.
|
|
49
|
+
TRANSIENT_RETRY_BASE_SECONDS = 1.0
|
|
50
|
+
TRANSIENT_RETRY_MAX_BACKOFF_SECONDS = 30.0
|
|
51
|
+
|
|
52
|
+
# Prefix for the machine-readable line ``cmd_elicit_ask`` writes to stderr
|
|
53
|
+
# immediately after creating the elicitation, before it starts polling. Kept
|
|
54
|
+
# as a module constant so producer (here) and any future consumer/tests stay
|
|
55
|
+
# in lockstep on the exact contract string.
|
|
56
|
+
ELICITATION_CREATED_STDERR_PREFIX = "UNIQUE_ELICITATION_CREATED"
|
|
57
|
+
|
|
40
58
|
# --- Visibility workaround for UN-19815 ---------------------------------
|
|
41
59
|
#
|
|
42
60
|
# The chat UI (as of 2026-04-21) only renders an elicitation when the host
|
|
@@ -632,6 +650,74 @@ def cmd_elicit_respond(
|
|
|
632
650
|
return f"elicit: {exc}"
|
|
633
651
|
|
|
634
652
|
|
|
653
|
+
def _is_transient_api_failure(exc: BaseException) -> bool:
|
|
654
|
+
"""Return ``True`` if *exc* is worth retrying.
|
|
655
|
+
|
|
656
|
+
Transient: connection-level failures (timeouts, resets --
|
|
657
|
+
``APIConnectionError``) and ``APIError``s with no HTTP status or a 5xx
|
|
658
|
+
status. Not transient: 4xx client errors, which cannot succeed on
|
|
659
|
+
retry -- retrying those would just burn the wait budget on a request
|
|
660
|
+
that is doomed regardless.
|
|
661
|
+
|
|
662
|
+
Note ``InvalidRequestError`` / ``AuthenticationError`` / ``PermissionError``
|
|
663
|
+
are not ``APIError`` subclasses and are not matched here; they propagate
|
|
664
|
+
unchanged, exactly as they did before this retry logic existed.
|
|
665
|
+
"""
|
|
666
|
+
if isinstance(exc, unique_sdk.APIConnectionError):
|
|
667
|
+
return True
|
|
668
|
+
if isinstance(exc, unique_sdk.APIError):
|
|
669
|
+
status = getattr(exc, "http_status", None)
|
|
670
|
+
return status is None or status >= 500
|
|
671
|
+
return False
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
def _get_elicitation_with_retry(
|
|
675
|
+
state: ShellState,
|
|
676
|
+
elicitation_id: str,
|
|
677
|
+
*,
|
|
678
|
+
deadline: float,
|
|
679
|
+
) -> dict[str, Any]:
|
|
680
|
+
"""Fetch an elicitation, retrying transient failures with backoff.
|
|
681
|
+
|
|
682
|
+
Retries are bounded by *deadline* (a ``time.monotonic()`` timestamp) --
|
|
683
|
+
once it passes, the last exception is re-raised so the caller's overall
|
|
684
|
+
``--timeout`` is always the hard outer bound, never exceeded by retries.
|
|
685
|
+
Non-transient failures (see :func:`_is_transient_api_failure`) are
|
|
686
|
+
re-raised immediately without retrying. Each retry is logged to stderr
|
|
687
|
+
with the attempt number and backoff so a string of dropped connections
|
|
688
|
+
is diagnosable instead of silently eating the wait budget.
|
|
689
|
+
"""
|
|
690
|
+
backoff = TRANSIENT_RETRY_BASE_SECONDS
|
|
691
|
+
attempt = 0
|
|
692
|
+
while True:
|
|
693
|
+
try:
|
|
694
|
+
return dict(
|
|
695
|
+
unique_sdk.Elicitation.get_elicitation(
|
|
696
|
+
user_id=state.config.user_id,
|
|
697
|
+
company_id=state.config.company_id,
|
|
698
|
+
elicitation_id=elicitation_id,
|
|
699
|
+
)
|
|
700
|
+
)
|
|
701
|
+
except (unique_sdk.APIError, unique_sdk.APIConnectionError) as exc:
|
|
702
|
+
if not _is_transient_api_failure(exc):
|
|
703
|
+
raise
|
|
704
|
+
remaining = deadline - time.monotonic()
|
|
705
|
+
if remaining <= 0:
|
|
706
|
+
raise
|
|
707
|
+
attempt += 1
|
|
708
|
+
sleep_for = max(
|
|
709
|
+
0.0, min(backoff, TRANSIENT_RETRY_MAX_BACKOFF_SECONDS, remaining)
|
|
710
|
+
)
|
|
711
|
+
print(
|
|
712
|
+
f"elicit: transient error on attempt {attempt} fetching "
|
|
713
|
+
f"{elicitation_id} ({exc}); retrying in {sleep_for:.1f}s",
|
|
714
|
+
file=sys.stderr,
|
|
715
|
+
)
|
|
716
|
+
if sleep_for > 0:
|
|
717
|
+
time.sleep(sleep_for)
|
|
718
|
+
backoff = min(backoff * 2, TRANSIENT_RETRY_MAX_BACKOFF_SECONDS)
|
|
719
|
+
|
|
720
|
+
|
|
635
721
|
def cmd_elicit_wait(
|
|
636
722
|
state: ShellState,
|
|
637
723
|
elicitation_id: str,
|
|
@@ -658,12 +744,10 @@ def cmd_elicit_wait(
|
|
|
658
744
|
try:
|
|
659
745
|
deadline = time.monotonic() + max(1, timeout)
|
|
660
746
|
while True:
|
|
661
|
-
elicitation =
|
|
662
|
-
|
|
663
|
-
company_id=state.config.company_id,
|
|
664
|
-
elicitation_id=elicitation_id,
|
|
747
|
+
elicitation = _get_elicitation_with_retry(
|
|
748
|
+
state, elicitation_id, deadline=deadline
|
|
665
749
|
)
|
|
666
|
-
last =
|
|
750
|
+
last = elicitation
|
|
667
751
|
status = str(elicitation.get("status", "")).upper()
|
|
668
752
|
if status in TERMINAL_STATUSES:
|
|
669
753
|
terminal_status = status
|
|
@@ -675,12 +759,8 @@ def cmd_elicit_wait(
|
|
|
675
759
|
# same instant; this fetch forces the backend's lazy expiry to
|
|
676
760
|
# run so we report a clean EXPIRED — and publish it to the chat
|
|
677
761
|
# subscription — instead of a stale PENDING.
|
|
678
|
-
final =
|
|
679
|
-
|
|
680
|
-
user_id=state.config.user_id,
|
|
681
|
-
company_id=state.config.company_id,
|
|
682
|
-
elicitation_id=elicitation_id,
|
|
683
|
-
)
|
|
762
|
+
final = _get_elicitation_with_retry(
|
|
763
|
+
state, elicitation_id, deadline=deadline
|
|
684
764
|
)
|
|
685
765
|
last = final
|
|
686
766
|
final_status = str(final.get("status", "")).upper()
|
|
@@ -703,7 +783,7 @@ def cmd_elicit_wait(
|
|
|
703
783
|
f"{format_elicitation(last)}"
|
|
704
784
|
)
|
|
705
785
|
time.sleep(poll_interval)
|
|
706
|
-
except unique_sdk.APIError as exc:
|
|
786
|
+
except (unique_sdk.APIError, unique_sdk.APIConnectionError) as exc:
|
|
707
787
|
return f"elicit: {exc}"
|
|
708
788
|
finally:
|
|
709
789
|
# If the poll loop exited before we ever received a response
|
|
@@ -720,7 +800,7 @@ def cmd_elicit_wait(
|
|
|
720
800
|
elicitation_id=elicitation_id,
|
|
721
801
|
)
|
|
722
802
|
)
|
|
723
|
-
except unique_sdk.APIError:
|
|
803
|
+
except (unique_sdk.APIError, unique_sdk.APIConnectionError):
|
|
724
804
|
last = None
|
|
725
805
|
ctx = _extract_visibility_context(last)
|
|
726
806
|
if ctx is not None:
|
|
@@ -735,6 +815,36 @@ def cmd_elicit_wait(
|
|
|
735
815
|
)
|
|
736
816
|
|
|
737
817
|
|
|
818
|
+
def _emit_elicitation_created_stderr_line(
|
|
819
|
+
elicitation_id: str,
|
|
820
|
+
expires_at: object,
|
|
821
|
+
expires_in_seconds: int,
|
|
822
|
+
) -> None:
|
|
823
|
+
"""Write the ``UNIQUE_ELICITATION_CREATED`` contract line to stderr.
|
|
824
|
+
|
|
825
|
+
Format (stable, greppable, one line, stderr only)::
|
|
826
|
+
|
|
827
|
+
UNIQUE_ELICITATION_CREATED id=<id> expires_at=<iso8601>
|
|
828
|
+
|
|
829
|
+
Prefers the platform-returned ``expiresAt`` (authoritative); falls back
|
|
830
|
+
to a locally computed timestamp if the platform omitted it, so the line
|
|
831
|
+
is always emitted with a usable value.
|
|
832
|
+
"""
|
|
833
|
+
if isinstance(expires_at, str) and expires_at:
|
|
834
|
+
resolved_expires_at = expires_at
|
|
835
|
+
else:
|
|
836
|
+
resolved_expires_at = (
|
|
837
|
+
(datetime.now(tz=timezone.utc) + timedelta(seconds=expires_in_seconds))
|
|
838
|
+
.isoformat(timespec="milliseconds")
|
|
839
|
+
.replace("+00:00", "Z")
|
|
840
|
+
)
|
|
841
|
+
print(
|
|
842
|
+
f"{ELICITATION_CREATED_STDERR_PREFIX} id={elicitation_id} "
|
|
843
|
+
f"expires_at={resolved_expires_at}",
|
|
844
|
+
file=sys.stderr,
|
|
845
|
+
)
|
|
846
|
+
|
|
847
|
+
|
|
738
848
|
def cmd_elicit_ask(
|
|
739
849
|
state: ShellState,
|
|
740
850
|
*,
|
|
@@ -745,6 +855,7 @@ def cmd_elicit_ask(
|
|
|
745
855
|
message_id: str | None = None,
|
|
746
856
|
timeout: int = DEFAULT_WAIT_TIMEOUT_SECONDS,
|
|
747
857
|
poll_interval: float = DEFAULT_POLL_INTERVAL_SECONDS,
|
|
858
|
+
expires_in_seconds: int | None = None,
|
|
748
859
|
metadata: list[tuple[str, str]] | None = None,
|
|
749
860
|
visible: bool = True,
|
|
750
861
|
assistant_id: str | None = None,
|
|
@@ -757,6 +868,23 @@ def cmd_elicit_ask(
|
|
|
757
868
|
free-text ``answer`` is used. This is the preferred entry point when an
|
|
758
869
|
agent needs to ask the user a clarifying question.
|
|
759
870
|
|
|
871
|
+
``expires_in_seconds``, when given, decouples the server-side expiry
|
|
872
|
+
(``expiresInSeconds`` sent to the platform) from ``timeout`` (how long
|
|
873
|
+
*this process* blocks polling). When omitted (the default), behaviour is
|
|
874
|
+
unchanged from before this parameter existed: the elicitation expires
|
|
875
|
+
exactly when the local wait gives up, coupling the two. Pass this when
|
|
876
|
+
the caller's own wait budget (e.g. a harness's foreground timeout) is
|
|
877
|
+
shorter than how long a human should realistically have to answer --
|
|
878
|
+
otherwise the elicitation expires under the user before they see it.
|
|
879
|
+
|
|
880
|
+
Immediately after creation (before polling starts), a single line is
|
|
881
|
+
written to stderr for callers that want the elicitation id without
|
|
882
|
+
polling the pending list:
|
|
883
|
+
|
|
884
|
+
UNIQUE_ELICITATION_CREATED id=<id> expires_at=<iso8601>
|
|
885
|
+
|
|
886
|
+
This is stderr-only and does not change stdout's format.
|
|
887
|
+
|
|
760
888
|
When ``chat_id`` is set and ``visible`` is ``True`` (the default), the
|
|
761
889
|
elicitation is wrapped in a placeholder thinking timeline so the chat UI
|
|
762
890
|
actually renders it — see the "Visibility workaround for UN-19815" note
|
|
@@ -778,16 +906,17 @@ def cmd_elicit_ask(
|
|
|
778
906
|
"required": ["answer"],
|
|
779
907
|
}
|
|
780
908
|
|
|
781
|
-
# `ask` exposes a single knob: `--timeout` is both how long
|
|
782
|
-
# when the record expires. The two are
|
|
783
|
-
#
|
|
784
|
-
#
|
|
785
|
-
#
|
|
786
|
-
#
|
|
787
|
-
#
|
|
788
|
-
#
|
|
789
|
-
|
|
790
|
-
|
|
909
|
+
# By default `ask` exposes a single knob: `--timeout` is both how long
|
|
910
|
+
# we wait and when the record expires. The two are the same unless the
|
|
911
|
+
# caller passes `--expires-in` explicitly, so the backend's default (5
|
|
912
|
+
# minutes) never leaves the record PENDING after we have stopped
|
|
913
|
+
# waiting — which would otherwise prevent the chat UI from flipping
|
|
914
|
+
# the prompt to EXPIRED and offering the user a way to continue. The
|
|
915
|
+
# poll loop below reads the freshly EXPIRED record and the elicitation
|
|
916
|
+
# subscription delivers the terminal status to the chat.
|
|
917
|
+
effective_expires_in_seconds = (
|
|
918
|
+
expires_in_seconds if expires_in_seconds is not None else timeout
|
|
919
|
+
)
|
|
791
920
|
|
|
792
921
|
user_metadata = _parse_metadata_pairs(metadata)
|
|
793
922
|
effective_message_id = message_id
|
|
@@ -836,7 +965,7 @@ def cmd_elicit_ask(
|
|
|
836
965
|
url=None,
|
|
837
966
|
chat_id=chat_id,
|
|
838
967
|
message_id=effective_message_id,
|
|
839
|
-
expires_in_seconds=
|
|
968
|
+
expires_in_seconds=effective_expires_in_seconds,
|
|
840
969
|
external_elicitation_id=None,
|
|
841
970
|
metadata=user_metadata,
|
|
842
971
|
)
|
|
@@ -856,6 +985,12 @@ def cmd_elicit_ask(
|
|
|
856
985
|
_cleanup_placeholder_if_needed()
|
|
857
986
|
return "elicit: platform did not return an elicitation id"
|
|
858
987
|
|
|
988
|
+
_emit_elicitation_created_stderr_line(
|
|
989
|
+
elicitation_id,
|
|
990
|
+
elicitation.get("expiresAt"),
|
|
991
|
+
effective_expires_in_seconds,
|
|
992
|
+
)
|
|
993
|
+
|
|
859
994
|
# ``cmd_elicit_wait`` handles its own placeholder teardown by
|
|
860
995
|
# reading the markers back from the elicitation's metadata.
|
|
861
996
|
return cmd_elicit_wait(
|
|
@@ -89,7 +89,10 @@ OVERVIEW_HELP = textwrap.dedent("""\
|
|
|
89
89
|
--chat-id / -c <id> Associated chat ID
|
|
90
90
|
--message-id / -m <id> Associated message ID
|
|
91
91
|
--timeout <seconds> Max wait time, also sets when the
|
|
92
|
-
request expires
|
|
92
|
+
request expires unless --expires-in
|
|
93
|
+
is given (default: 7200)
|
|
94
|
+
--expires-in <seconds> Decouple server-side expiry from
|
|
95
|
+
--timeout (defaults to --timeout)
|
|
93
96
|
--poll-interval <seconds> Poll frequency (default: 2.0)
|
|
94
97
|
--metadata key=value Metadata (repeatable)
|
|
95
98
|
--no-visible Skip the UN-19815 visibility workaround
|
|
@@ -814,9 +817,12 @@ class UniqueShell(cmd.Cmd):
|
|
|
814
817
|
--mode FORM|URL Display mode (create only, default: FORM)
|
|
815
818
|
--chat-id / -c <id> Associated chat ID
|
|
816
819
|
--message-id / -m <id> Associated message ID
|
|
817
|
-
--expires-in <seconds> Auto-expire the request
|
|
820
|
+
--expires-in <seconds> Auto-expire the request. For ask, this
|
|
821
|
+
decouples expiry from --timeout
|
|
822
|
+
(defaults to --timeout when omitted)
|
|
818
823
|
--timeout <seconds> (ask / wait) max wait time, default 7200;
|
|
819
824
|
for ask this also sets when it expires
|
|
825
|
+
unless --expires-in is given
|
|
820
826
|
--poll-interval <seconds> (ask / wait) poll frequency, default 2
|
|
821
827
|
--external-id <id> External identifier (create only)
|
|
822
828
|
--metadata key=value Metadata (repeatable)
|
|
@@ -999,13 +1005,6 @@ class UniqueShell(cmd.Cmd):
|
|
|
999
1005
|
if not message:
|
|
1000
1006
|
self._print("Usage: elicit ask <message> [options]")
|
|
1001
1007
|
return
|
|
1002
|
-
if opts["expires_in_seconds"] is not None:
|
|
1003
|
-
self._print(
|
|
1004
|
-
"No such option: --expires-in for 'elicit ask'. Use --timeout "
|
|
1005
|
-
"(it also sets when the request expires). For expiry decoupled "
|
|
1006
|
-
"from a local wait, use 'elicit create --expires-in'."
|
|
1007
|
-
)
|
|
1008
|
-
return
|
|
1009
1008
|
|
|
1010
1009
|
ask_kwargs: dict[str, Any] = {
|
|
1011
1010
|
"message": message,
|
|
@@ -1014,6 +1013,7 @@ class UniqueShell(cmd.Cmd):
|
|
|
1014
1013
|
"chat_id": opts["chat_id"],
|
|
1015
1014
|
"message_id": opts["message_id"],
|
|
1016
1015
|
"timeout": opts["timeout"],
|
|
1016
|
+
"expires_in_seconds": opts["expires_in_seconds"],
|
|
1017
1017
|
"poll_interval": opts["poll_interval"],
|
|
1018
1018
|
"metadata": opts["metadata"] or None,
|
|
1019
1019
|
"visible": opts["visible"],
|
|
@@ -6,8 +6,10 @@ description: >-
|
|
|
6
6
|
(especially destructive actions), missing parameters, multiple-choice
|
|
7
7
|
decisions, or structured form input. Elicitations are routed through
|
|
8
8
|
the Unique AI Platform UI via `unique-cli elicit create` + `elicit wait`
|
|
9
|
-
(
|
|
10
|
-
|
|
9
|
+
(the default polling pattern) or a single supervised `unique-cli elicit
|
|
10
|
+
ask` call (only when your agent instructions say your environment
|
|
11
|
+
supervises the wait) so the user gets a proper structured prompt and
|
|
12
|
+
you get a structured answer back.
|
|
11
13
|
Do NOT ask the user in plain chat when you can use this skill instead.
|
|
12
14
|
---
|
|
13
15
|
|
|
@@ -15,22 +17,55 @@ description: >-
|
|
|
15
17
|
|
|
16
18
|
Use this skill **whenever you need input from the user** -- a clarifying question, a confirmation before a destructive action, a choice between options, or a structured form. Elicitations create a first-class UI prompt on the Unique AI Platform and return the answer as structured JSON.
|
|
17
19
|
|
|
18
|
-
> **Rule of thumb:** if you catch yourself about to write "Could you clarify…?" or "Do you want me to…?" or "Which one should I pick?" in chat, stop and use
|
|
20
|
+
> **Rule of thumb:** if you catch yourself about to write "Could you clarify…?" or "Do you want me to…?" or "Which one should I pick?" in chat, stop and use an elicitation instead (see "Two patterns" below for which command sequence to use).
|
|
19
21
|
|
|
20
22
|
!!! danger "`--visible` is currently MANDATORY"
|
|
21
23
|
You **must always create elicitations with visibility on** (it is on by default for both `elicit create` and `elicit ask` — just don't pass `--no-visible`). Until the UN-19815 UI fix ships in your environment, an elicitation created without the visibility workaround is stored by the backend but **never rendered in the chat UI** — the user simply never sees the question and you will wait forever. There is no situation in which you should disable it today.
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
!!! danger "A non-terminal status is not an answer"
|
|
26
|
+
This is the single rule that matters most, in **every** pattern below:
|
|
27
|
+
`PENDING` is not `DECLINED`, is not an answer, and is never a reason to
|
|
28
|
+
stop or proceed. Never treat a non-terminal status as a decline, never
|
|
29
|
+
proceed on an unanswered form, and never fabricate what you think the
|
|
30
|
+
user would say. If the last known status is `PENDING`, the only correct
|
|
31
|
+
next action is to keep waiting -- either loop `elicit wait` again
|
|
32
|
+
(Pattern A) or re-issue `elicit wait <id>` on the same elicitation
|
|
33
|
+
(Pattern B) -- until you observe one of the terminal statuses in
|
|
34
|
+
"Reading the response" below.
|
|
35
|
+
|
|
36
|
+
## Two patterns -- your agent instructions decide which one to use
|
|
37
|
+
|
|
38
|
+
There are two valid ways to wait for an elicitation's answer. **Which one you
|
|
39
|
+
use is decided by your agent instructions (system prompt / `AGENTS.md`), not
|
|
40
|
+
by this skill file guessing which harness you're running in:**
|
|
41
|
+
|
|
42
|
+
- **Your instructions are silent, or say nothing about your environment
|
|
43
|
+
supervising long-running waits** -- use **Pattern A: `elicit create` +
|
|
44
|
+
short polling loop with `elicit wait`** below. This is the default and is
|
|
45
|
+
safe everywhere, including inside a harness whose Bash/shell tool has its
|
|
46
|
+
own foreground-wait timeout (Claude Code, Codex, ~2 minutes commonly).
|
|
47
|
+
- **Your instructions explicitly say your environment supervises the wait**
|
|
48
|
+
for a single long-running tool call (it will not silently background or
|
|
49
|
+
kill the process before the human answers) -- use **Pattern B: a single
|
|
50
|
+
supervised `elicit ask` call** below instead.
|
|
51
|
+
|
|
52
|
+
If in doubt, use Pattern A -- it is correct in every environment, whereas
|
|
53
|
+
Pattern B is only correct in one that actually supervises the wait. Neither
|
|
54
|
+
pattern is forbidden; pick the one your instructions call for.
|
|
55
|
+
|
|
56
|
+
## Pattern A: `elicit create` + short polling loop with `elicit wait` (default)
|
|
24
57
|
|
|
25
58
|
Do **not** call `elicit ask` from inside an agent harness (Claude Code, Codex,
|
|
26
59
|
or any environment where your Bash/shell tool has its own foreground-wait
|
|
27
|
-
timeout — commonly ~2 minutes)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
60
|
+
timeout — commonly ~2 minutes) **unless your agent instructions explicitly
|
|
61
|
+
say your environment supervises the wait (see Pattern B below)**. `elicit
|
|
62
|
+
ask` blocks synchronously for up to `--timeout` seconds (default 2 hours)
|
|
63
|
+
waiting for the human to answer, but a human reading and answering a prompt
|
|
64
|
+
routinely takes longer than a typical Bash tool timeout. If that timeout
|
|
65
|
+
fires first and your instructions did not say otherwise, your harness will
|
|
66
|
+
silently detach the process to the background and hand you a "running in
|
|
67
|
+
background" stub instead of the real answer — you will not see the user's
|
|
68
|
+
response, and the chat may appear stuck to the user.
|
|
34
69
|
|
|
35
70
|
Instead, use `elicit create` (returns immediately) followed by a short
|
|
36
71
|
polling loop with `elicit wait`, where **every individual command finishes
|
|
@@ -86,7 +121,9 @@ default — comfortably under Claude Code's ~120s):
|
|
|
86
121
|
`elicit ask` remains the right choice for **non-agent, scripted, or
|
|
87
122
|
human-operated CLI usage** where a single blocking call is expected and
|
|
88
123
|
there is no surrounding tool-timeout concern (tests, ops scripts, manual CLI
|
|
89
|
-
use)
|
|
124
|
+
use), and for **Pattern B below** when your instructions say your
|
|
125
|
+
environment supervises the wait. Do not reach for it from inside an agent
|
|
126
|
+
turn unless one of those two conditions applies.
|
|
90
127
|
|
|
91
128
|
```bash
|
|
92
129
|
unique-cli elicit ask "<question>" [options]
|
|
@@ -99,6 +136,43 @@ unique-cli elicit ask "<question>" [options]
|
|
|
99
136
|
(preferred) or `$UNIQUE_MESSAGE_ID`. Do **not** pass a stale
|
|
100
137
|
`$UNIQUE_MESSAGE_ID` from a persistent process environment.
|
|
101
138
|
|
|
139
|
+
## Pattern B: a single supervised `elicit ask` call
|
|
140
|
+
|
|
141
|
+
Use this **only when your agent instructions explicitly say your
|
|
142
|
+
environment supervises the wait** for a single long-running tool call --
|
|
143
|
+
i.e. it will not silently background or kill the process before the human
|
|
144
|
+
answers. Do not use Pattern B just because it is simpler; use it only
|
|
145
|
+
because your instructions told you your environment can safely block for
|
|
146
|
+
the full wait.
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
unique-cli elicit ask "<question>" \
|
|
150
|
+
--chat-id "$UNIQUE_CHAT_ID" \
|
|
151
|
+
--expires-in 7200 \
|
|
152
|
+
--timeout 1800
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
- **`--expires-in`** sets how long the request stays live on the platform
|
|
156
|
+
(a human-scale deadline, e.g. `7200` = 2 hours) independently of
|
|
157
|
+
**`--timeout`**, which is how long *this call* blocks. Set `--timeout` to
|
|
158
|
+
match your supervisor's own wait budget; omit `--expires-in` only if you
|
|
159
|
+
want it to default to `--timeout` (today's coupled behavior, unchanged).
|
|
160
|
+
- Immediately after creating the elicitation -- before it starts waiting --
|
|
161
|
+
the CLI writes one line to **stderr** (never stdout):
|
|
162
|
+
`UNIQUE_ELICITATION_CREATED id=<id> expires_at=<iso8601>`. A supervisor
|
|
163
|
+
watching stderr can capture the elicitation id the instant it exists,
|
|
164
|
+
without waiting for `ask` to return.
|
|
165
|
+
- While waiting, transient failures (connection errors, timeouts, 5xx) are
|
|
166
|
+
retried automatically with bounded exponential backoff and logged to
|
|
167
|
+
stderr -- you do not need your own retry loop around `ask` to survive a
|
|
168
|
+
single dropped connection. 4xx errors are not retried.
|
|
169
|
+
- The result of the single call **is** the answer. Check `Status:` exactly
|
|
170
|
+
as described in "Reading the response" below, and the same rule applies
|
|
171
|
+
if `ask` still returns `PENDING` when `--timeout` elapses: that is **not**
|
|
172
|
+
a decline and not a reason to stop -- the elicitation is still live on
|
|
173
|
+
the platform (until `--expires-in`), so re-issue `elicit wait <id>` on the
|
|
174
|
+
same id to keep waiting for the real answer.
|
|
175
|
+
|
|
102
176
|
## When to use
|
|
103
177
|
|
|
104
178
|
| Situation | Use elicitation? |
|
|
@@ -113,9 +187,11 @@ unique-cli elicit ask "<question>" [options]
|
|
|
113
187
|
## Examples
|
|
114
188
|
|
|
115
189
|
> The examples below use `elicit ask` for brevity to show the schema shapes.
|
|
116
|
-
> From an agent harness, use the
|
|
117
|
-
>
|
|
118
|
-
> shown in "
|
|
190
|
+
> From an agent harness whose instructions are silent on supervision, use the
|
|
191
|
+
> same `--schema`/`--message`/`--tool-name` arguments with `elicit create`
|
|
192
|
+
> instead, then poll with `elicit wait` as shown in "Pattern A" above (or use
|
|
193
|
+
> `elicit ask` directly per "Pattern B" if your instructions say your
|
|
194
|
+
> environment supervises the wait).
|
|
119
195
|
|
|
120
196
|
### Minimal — free-text answer
|
|
121
197
|
|
|
@@ -173,7 +249,7 @@ unique-cli elicit ask "Permanently delete /Archive/2024 and everything inside it
|
|
|
173
249
|
--schema '{"type": "object", "properties": {}}'
|
|
174
250
|
```
|
|
175
251
|
|
|
176
|
-
Proceed **only** if the `Status:` is `ACCEPTED`. Treat `DECLINED`, `CANCELLED`, or `EXPIRED` all as "do not proceed" -- tell the user you stopped and return control. Put everything the user needs to decide into the message text, since the form has no fields.
|
|
252
|
+
Proceed **only** if the `Status:` is `ACCEPTED`. Treat `DECLINED`, `REJECTED`, `CANCELLED`, or `EXPIRED` all as "do not proceed" -- tell the user you stopped and return control. Put everything the user needs to decide into the message text, since the form has no fields.
|
|
177
253
|
|
|
178
254
|
### Structured form (multiple fields)
|
|
179
255
|
|
|
@@ -194,11 +270,12 @@ unique-cli elicit ask "Please provide report settings" \
|
|
|
194
270
|
|
|
195
271
|
## Options
|
|
196
272
|
|
|
197
|
-
The table below documents `elicit ask`'s flags
|
|
198
|
-
same `--chat-id`,
|
|
199
|
-
|
|
200
|
-
`--
|
|
201
|
-
|
|
273
|
+
The table below documents `elicit ask`'s flags (used in both Pattern A's
|
|
274
|
+
"scripting" case and Pattern B). `elicit create` takes the same `--chat-id`,
|
|
275
|
+
`--message-id`, `--tool-name`, `--schema`, `--metadata`, and `--assistant-id`
|
|
276
|
+
flags, plus `--expires-in <seconds>` (`elicit create` has no `--timeout`/
|
|
277
|
+
`--poll-interval` -- those two apply only to `elicit wait`, which you call
|
|
278
|
+
separately in the Pattern A polling loop). `elicit create` also takes
|
|
202
279
|
`--mode FORM|URL` (default: `FORM` — you only need to pass it for URL
|
|
203
280
|
elicitations).
|
|
204
281
|
|
|
@@ -208,7 +285,8 @@ elicitations).
|
|
|
208
285
|
| `--message-id` | `-m` | auto | Optional. Prefer omitting this flag — the CLI resolves the current turn's message ID from `$UNIQUE_TURN_IDENTITY_FILE` (preferred) or `$UNIQUE_MESSAGE_ID`. Do not pass a stale env value from a persistent process. |
|
|
209
286
|
| `--tool-name` | `-t` | `agent_question` | Short snake_case label shown to the user (e.g. `clarify`, `confirm_delete`, `choose_report`). |
|
|
210
287
|
| `--schema` | | single `answer` string | JSON Schema for the form body. |
|
|
211
|
-
| `--
|
|
288
|
+
| `--expires-in` | | `--timeout`'s value | Seconds before the platform auto-expires the request, decoupled from `--timeout`. This is the flag Pattern B uses to give the elicitation a human-scale deadline (e.g. `7200`) independent of the caller's own wait budget. Omit it to keep today's coupled behavior. |
|
|
289
|
+
| `--timeout` | | `7200` | Max seconds to block locally before giving up. Unless you pass `--expires-in`, this is also 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. |
|
|
212
290
|
| `--poll-interval` | | `2.0` | Seconds between status polls. |
|
|
213
291
|
| `--metadata` | | none | `key=value` metadata (repeatable). |
|
|
214
292
|
| `--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. |
|
|
@@ -230,12 +308,19 @@ Terminal statuses:
|
|
|
230
308
|
|
|
231
309
|
| Status | Meaning | What to do |
|
|
232
310
|
|--------|---------|------------|
|
|
233
|
-
| `RESPONDED` / `COMPLETED` | User answered | Parse `Response:` JSON and proceed. |
|
|
234
|
-
| `DECLINED` | User explicitly declined | Do not proceed. Acknowledge and stop. |
|
|
311
|
+
| `RESPONDED` / `ACCEPTED` / `COMPLETED` | User answered, or confirmed (accepted) an empty-schema confirmation | Parse `Response:` JSON and proceed. |
|
|
312
|
+
| `DECLINED` / `REJECTED` | User explicitly declined, or rejected (declined) an empty-schema confirmation | Do not proceed. Acknowledge and stop. |
|
|
235
313
|
| `CANCELLED` | Cancelled (by user or system) | Do not proceed. |
|
|
236
|
-
| `EXPIRED` | Timed out on the platform — the user did not answer
|
|
314
|
+
| `EXPIRED` | Timed out on the platform — the user did not answer before the request's expiry (`--timeout`, or `--expires-in` if you passed it) | Ask again only if the task still needs it; do not treat the expiry as approval. |
|
|
315
|
+
|
|
316
|
+
`ACCEPTED`/`REJECTED` and `RESPONDED`/`DECLINED` are functionally
|
|
317
|
+
equivalent terminal outcomes -- `ACCEPTED`/`REJECTED` are the accept/decline
|
|
318
|
+
synonyms produced by some response paths (e.g. the Codex approval-bridge
|
|
319
|
+
integration), while `RESPONDED`/`DECLINED` are produced by others. Treat
|
|
320
|
+
them the same way: `ACCEPTED` like `RESPONDED` (proceed), `REJECTED` like
|
|
321
|
+
`DECLINED` (stop).
|
|
237
322
|
|
|
238
|
-
Because `ask` derives the request's expiry from `--timeout
|
|
323
|
+
Because `ask` derives the request's expiry from `--timeout` (unless you pass `--expires-in`), 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 ...` (Pattern B) or `elicit: still PENDING after Ns ...` (Pattern A's `elicit wait`), that is **not** `EXPIRED` -- see the danger box at the top of this file: keep waiting, do not treat it as a stopping condition. 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.
|
|
239
324
|
|
|
240
325
|
### Repeat the answer back in chat
|
|
241
326
|
|
|
@@ -266,10 +351,12 @@ Do not expose raw JSON by default when a natural-language confirmation would be
|
|
|
266
351
|
|
|
267
352
|
This one-shot pattern is for **scripts, tests, or manual CLI use outside an
|
|
268
353
|
agent harness** — i.e. contexts with no Bash-tool foreground timeout to worry
|
|
269
|
-
about. From inside an agent harness
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
354
|
+
about. From inside an agent harness whose instructions are silent on
|
|
355
|
+
supervision, use the `elicit create` + `elicit wait` polling pattern from
|
|
356
|
+
"Pattern A" above instead (or "Pattern B" if your instructions say your
|
|
357
|
+
environment supervises the wait); the output parsing below (pulling
|
|
358
|
+
`Response:` out of the text) is identical either way, only the command(s)
|
|
359
|
+
producing that output differ.
|
|
273
360
|
|
|
274
361
|
In a shell script or agent tool wrapper, capture the output and pull out the `Response:` line:
|
|
275
362
|
|
|
@@ -301,7 +388,7 @@ esac
|
|
|
301
388
|
|
|
302
389
|
## Agent workflow rules
|
|
303
390
|
|
|
304
|
-
1. **Default to `elicit create` + `elicit wait` polling.** If you need an answer from the user, use this pattern, not a chat message
|
|
391
|
+
1. **Default to `elicit create` + `elicit wait` polling (Pattern A).** If you need an answer from the user, use this pattern, not a chat message -- unless your agent instructions explicitly say your environment supervises the wait, in which case use a single `elicit ask` call (Pattern B). See "Two patterns" above.
|
|
305
392
|
2. **Always pass `--chat-id "$UNIQUE_CHAT_ID"`.** Without it the elicitation is not attached to a chat and the user will not see it.
|
|
306
393
|
3. **Omit `--message-id`.** The CLI resolves the current turn's assistant message ID from `$UNIQUE_TURN_IDENTITY_FILE` (preferred) or `$UNIQUE_MESSAGE_ID`. Do not pass a stale `$UNIQUE_MESSAGE_ID` from a persistent process environment.
|
|
307
394
|
4. **Never pass `--no-visible`.** See the warning above. The visibility workaround is mandatory today.
|
|
@@ -309,10 +396,10 @@ esac
|
|
|
309
396
|
6. **Pick a meaningful `--tool-name`.** `confirm_delete`, `choose_region`, `pick_report` -- short snake_case describing the intent.
|
|
310
397
|
7. **Constrain answers with a schema** whenever the valid set is finite -- don't rely on parsing free text when `enum` is an option.
|
|
311
398
|
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.
|
|
312
|
-
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.
|
|
399
|
+
9. **Handle non-`RESPONDED`/`ACCEPTED` outcomes explicitly.** If the status is `DECLINED` / `REJECTED` / `CANCELLED` / `EXPIRED`, tell the user you stopped and ask what they want to do next instead of silently proceeding. A non-terminal (`PENDING`) status is never one of these outcomes -- see the danger box at the top of this file.
|
|
313
400
|
10. **Don't spam elicitations.** One well-designed form with a few related fields is better than five sequential yes/no questions.
|
|
314
401
|
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.
|
|
315
|
-
12. **Never make a single blocking call longer than your harness's Bash foreground timeout.**
|
|
402
|
+
12. **Never make a single blocking call longer than your harness's Bash foreground timeout, unless your instructions say your environment supervises the wait.** In Pattern A, use `--expires-in` on `elicit create` to set the real, human-scale deadline (e.g. 7200s / 2 hours), but keep each individual `elicit wait --timeout N` call short (≈90s) and loop until a terminal status or the overall deadline is reached. In Pattern B, `--expires-in` on `elicit ask` does the same job, decoupled from the (now safely long) `--timeout` your supervisor manages. Never call `elicit ask` with a multi-minute `--timeout` from an agent harness unless Pattern B applies.
|
|
316
403
|
|
|
317
404
|
## Prerequisites
|
|
318
405
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/__init__.py
RENAMED
|
File without changes
|
{unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_acronyms.py
RENAMED
|
File without changes
|
{unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_agentic_table.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_benchmarking.py
RENAMED
|
File without changes
|
{unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_briefing.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_content.py
RENAMED
|
File without changes
|
{unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_elicitation.py
RENAMED
|
File without changes
|
{unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_embedding.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_folder.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_integrated.py
RENAMED
|
File without changes
|
{unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_llm_models.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_message.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_message_log.py
RENAMED
|
File without changes
|
{unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_message_tool.py
RENAMED
|
File without changes
|
{unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_module.py
RENAMED
|
File without changes
|
{unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_scheduled_task.py
RENAMED
|
File without changes
|
{unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_search.py
RENAMED
|
File without changes
|
{unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_search_string.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/api_resources/_web_search.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/commands/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/commands/agentic_table.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/commands/cite_file.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/commands/navigation.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/commands/scheduled_tasks.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/commands/subagent.py
RENAMED
|
File without changes
|
{unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/cli/commands/web_search.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/utils/analytics_order_run.py
RENAMED
|
File without changes
|
{unique_sdk-2026.32.0.dev9 → unique_sdk-2026.32.0.dev11}/unique_sdk/utils/benchmarking_run.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|