unique-sdk 2026.46.0.dev2__tar.gz → 2026.46.0.dev4__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.46.0.dev2 → unique_sdk-2026.46.0.dev4}/PKG-INFO +1 -1
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/pyproject.toml +1 -1
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_space.py +4 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/commands/navigation.py +10 -1
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/commands/search.py +3 -5
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/metadata_filter.py +79 -18
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/state.py +19 -5
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/README.md +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/__init__.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/_api_requestor.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/_api_resource.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/_api_version.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/_error.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/_http_client.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/_list_object.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/_object_classes.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/_request_options.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/_unique_object.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/_unique_ql.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/_unique_response.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/_util.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/_version.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/_webhook.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/__init__.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_acronyms.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_agentic_table.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_analytics_order.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_benchmarking.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_briefing.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_chat_completion.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_content.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_context_memory.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_elicitation.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_embedding.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_event.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_folder.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_group.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_integrated.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_llm_models.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_mcp.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_message.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_message_assessment.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_message_execution.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_message_log.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_message_tool.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_module.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_scheduled_task.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_search.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_search_string.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_short_term_memory.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_user.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_web_search.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/__init__.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/__main__.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/cli.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/commands/__init__.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/commands/_citation_manifest.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/commands/agentic_table.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/commands/agentic_table_write.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/commands/browser.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/commands/cite_file.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/commands/elicitation.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/commands/files.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/commands/folders.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/commands/mcp.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/commands/read.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/commands/scheduled_tasks.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/commands/subagent.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/commands/web_search.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/commands/web_search_config.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/config.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/formatting.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/identity.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/paths.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/shell.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/skills/unique-cli-agentic-table/SKILL.md +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/skills/unique-cli-elicitation/SKILL.md +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/skills/unique-cli-file-management/SKILL.md +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/skills/unique-cli-mcp/SKILL.md +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/skills/unique-cli-scheduled-tasks/SKILL.md +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/skills/unique-cli-search/SKILL.md +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/skills/unique-cli-subagent/SKILL.md +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/skills/unique-cli-uploaded-search/SKILL.md +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/skills/unique-cli-web-search/SKILL.md +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/workspace.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/utils/analytics_order_run.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/utils/benchmarking_run.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/utils/chat_history.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/utils/chat_in_space.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/utils/file_io.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/utils/sources.py +0 -0
- {unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/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.46.0.
|
|
3
|
+
Version: 2026.46.0.dev4
|
|
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>
|
|
@@ -271,9 +271,13 @@ class Space(APIResource["Space"]):
|
|
|
271
271
|
|
|
272
272
|
Attributes:
|
|
273
273
|
title: New chat title. Must be a non-empty string.
|
|
274
|
+
isGenerated: Marks the title as generated by an assistant. It is
|
|
275
|
+
applied only while the chat still has its prompt-based title;
|
|
276
|
+
otherwise the request fails with HTTP 409.
|
|
274
277
|
"""
|
|
275
278
|
|
|
276
279
|
title: str
|
|
280
|
+
isGenerated: NotRequired[bool]
|
|
277
281
|
|
|
278
282
|
class ChatResult(TypedDict):
|
|
279
283
|
id: str
|
{unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/commands/navigation.py
RENAMED
|
@@ -338,16 +338,23 @@ def cmd_ls(
|
|
|
338
338
|
# Folder-only filters keep every file (each passes the folderIdPath
|
|
339
339
|
# leaf); the verdict is cached per content id. See UN-21780.
|
|
340
340
|
# The paging note counts rows read from the server, before the scope filter.
|
|
341
|
+
scanned_folders = len(folders)
|
|
341
342
|
scanned_files = len(files)
|
|
342
343
|
if state.workspace_metadata_filter is not None:
|
|
344
|
+
folders = [
|
|
345
|
+
folder
|
|
346
|
+
for folder in folders
|
|
347
|
+
if state.folder_allowed_by_metadata_filter(str(folder.get("id", "")))
|
|
348
|
+
]
|
|
343
349
|
files = [
|
|
344
350
|
f for f in files if state.is_content_within_workspace(f.get("id", ""))
|
|
345
351
|
]
|
|
352
|
+
hidden_folders = scanned_folders - len(folders)
|
|
346
353
|
note = _window_note(
|
|
347
354
|
target if target is not None else resolved_path,
|
|
348
355
|
skip,
|
|
349
356
|
limit,
|
|
350
|
-
{"folders":
|
|
357
|
+
{"folders": scanned_folders, "files": scanned_files},
|
|
351
358
|
{"folders": total_folders, "files": total_files},
|
|
352
359
|
hidden_files=scanned_files - len(files),
|
|
353
360
|
)
|
|
@@ -363,6 +370,8 @@ def cmd_ls(
|
|
|
363
370
|
f"\n{total_folders} folder(s), {len(files)} file(s) in task scope "
|
|
364
371
|
f"(of {total_files} in folder)"
|
|
365
372
|
)
|
|
373
|
+
if hidden_folders:
|
|
374
|
+
summary += f" ({hidden_folders} folder(s) hidden by task scope)"
|
|
366
375
|
return output + summary + note
|
|
367
376
|
|
|
368
377
|
summary = f"\n{total_folders} folder(s), {total_files} file(s)"
|
|
@@ -216,11 +216,9 @@ def cmd_search(
|
|
|
216
216
|
folder_scope_id = _resolve_folder_to_scope_id(state, folder)
|
|
217
217
|
elif state.scope_id and not state.workspace_metadata_filter:
|
|
218
218
|
# The incidental cwd narrows search only when there is no
|
|
219
|
-
# per-message filter.
|
|
220
|
-
#
|
|
221
|
-
#
|
|
222
|
-
# rather than a scope mismatch. With a filter active, only an
|
|
223
|
-
# explicit --folder narrows further. See UN-21780.
|
|
219
|
+
# per-message filter. With a filter active, only an explicit
|
|
220
|
+
# --folder narrows further: ANDing the cwd would turn a scope
|
|
221
|
+
# mismatch into an empty result. See UN-21780.
|
|
224
222
|
folder_scope_id = state.scope_id
|
|
225
223
|
|
|
226
224
|
scope_ids: list[str] | None = None
|
|
@@ -127,18 +127,54 @@ def _collect_navigable_folder_ids(
|
|
|
127
127
|
return folder_ids
|
|
128
128
|
if _is_negated_operator(node.get("operator")):
|
|
129
129
|
return folder_ids
|
|
130
|
+
for scope_id in _folder_scope_ids(node):
|
|
131
|
+
if scope_id not in folder_ids:
|
|
132
|
+
folder_ids.append(scope_id)
|
|
133
|
+
return folder_ids
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _folder_scope_ids(node: dict[Any, Any]) -> list[str]:
|
|
137
|
+
"""Folder scope ids named by a ``folderIdPath`` leaf, else an empty list."""
|
|
130
138
|
path = node.get("path")
|
|
131
139
|
field = path[0] if isinstance(path, list) and path else path
|
|
132
|
-
if field
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
140
|
+
if field != "folderIdPath":
|
|
141
|
+
return []
|
|
142
|
+
value = node.get("value")
|
|
143
|
+
values = value if isinstance(value, list) else [value]
|
|
144
|
+
scope_ids: list[str] = []
|
|
145
|
+
for item in values:
|
|
146
|
+
scope_id = _extract_target_scope_id(item)
|
|
147
|
+
if scope_id and scope_id not in scope_ids:
|
|
148
|
+
scope_ids.append(scope_id)
|
|
149
|
+
return scope_ids
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _collect_excluded_folder_ids(
|
|
153
|
+
node: Any, folder_ids: list[str] | None = None
|
|
154
|
+
) -> list[str]:
|
|
155
|
+
"""Every negated ``folderIdPath`` leaf in the tree."""
|
|
156
|
+
if folder_ids is None:
|
|
157
|
+
folder_ids = []
|
|
158
|
+
if not isinstance(node, dict):
|
|
159
|
+
return folder_ids
|
|
160
|
+
for key in ("and", "or"):
|
|
161
|
+
if key in node:
|
|
162
|
+
for child in node.get(key) or []:
|
|
163
|
+
_collect_excluded_folder_ids(child, folder_ids)
|
|
164
|
+
return folder_ids
|
|
165
|
+
if not _is_negated_operator(node.get("operator")):
|
|
166
|
+
return folder_ids
|
|
167
|
+
for scope_id in _folder_scope_ids(node):
|
|
168
|
+
if scope_id not in folder_ids:
|
|
169
|
+
folder_ids.append(scope_id)
|
|
139
170
|
return folder_ids
|
|
140
171
|
|
|
141
172
|
|
|
173
|
+
def _path_is_inside_any(target: str, roots: list[str]) -> bool:
|
|
174
|
+
"""True when *target* is one of *roots* or a descendant of one."""
|
|
175
|
+
return any(target == root or target.startswith(root + "/") for root in roots)
|
|
176
|
+
|
|
177
|
+
|
|
142
178
|
def _collect_filter_targets(
|
|
143
179
|
node: Any,
|
|
144
180
|
folder_ids: list[str] | None = None,
|
|
@@ -357,9 +393,15 @@ class MetadataFilter:
|
|
|
357
393
|
"""Folder scope ids the filter grants for navigation (ls/cd into).
|
|
358
394
|
|
|
359
395
|
Excludes folders only reachable as an ``or`` alternative to a
|
|
360
|
-
``contentId`` allowlist, which are not standalone-browsable.
|
|
396
|
+
``contentId`` allowlist, which are not standalone-browsable. Also
|
|
397
|
+
drops a grant named by any exclusion in the tree.
|
|
361
398
|
"""
|
|
362
|
-
|
|
399
|
+
excluded = set(_collect_excluded_folder_ids(self._tree))
|
|
400
|
+
return [
|
|
401
|
+
scope_id
|
|
402
|
+
for scope_id in _collect_navigable_folder_ids(self._tree)
|
|
403
|
+
if scope_id not in excluded
|
|
404
|
+
]
|
|
363
405
|
|
|
364
406
|
def _navigable_folder_paths(self) -> list[str]:
|
|
365
407
|
return [
|
|
@@ -370,21 +412,35 @@ class MetadataFilter:
|
|
|
370
412
|
if p
|
|
371
413
|
]
|
|
372
414
|
|
|
373
|
-
def
|
|
374
|
-
|
|
415
|
+
def _excluded_scope_ids(self) -> list[str]:
|
|
416
|
+
return _collect_excluded_folder_ids(self._tree)
|
|
375
417
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
418
|
+
def _resolved_exclusion_paths(self) -> list[str] | None:
|
|
419
|
+
"""Exclusion paths, or ``None`` when any exclusion failed to resolve."""
|
|
420
|
+
paths: list[str] = []
|
|
421
|
+
for scope_id in self._excluded_scope_ids():
|
|
422
|
+
path = self._resolve_scope_path(scope_id)
|
|
423
|
+
if not path:
|
|
424
|
+
return None
|
|
425
|
+
paths.append(path)
|
|
426
|
+
return paths
|
|
427
|
+
|
|
428
|
+
def allows_folder_scope(self, scope_id: str) -> bool:
|
|
429
|
+
"""True if *scope_id* is in a navigable scope and not under an exclusion."""
|
|
430
|
+
if scope_id in self._excluded_scope_ids():
|
|
431
|
+
return False
|
|
432
|
+
excluded_paths = self._resolved_exclusion_paths()
|
|
433
|
+
if excluded_paths is None:
|
|
434
|
+
return False
|
|
379
435
|
folder_paths = self._navigable_folder_paths()
|
|
380
436
|
if not folder_paths:
|
|
381
437
|
return False
|
|
382
438
|
target_path = self._resolve_scope_path(scope_id)
|
|
383
439
|
if not target_path:
|
|
384
440
|
return False
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
)
|
|
441
|
+
if _path_is_inside_any(target_path, excluded_paths):
|
|
442
|
+
return False
|
|
443
|
+
return _path_is_inside_any(target_path, folder_paths)
|
|
388
444
|
|
|
389
445
|
def allows_folder_path(self, folder_path: str) -> bool:
|
|
390
446
|
"""True if an absolute folder *path* lies within a navigable scope.
|
|
@@ -393,11 +449,16 @@ class MetadataFilter:
|
|
|
393
449
|
scope id yet (e.g. a ``mkdir`` destination), so a ``..`` traversal can't
|
|
394
450
|
create structure outside the task scope.
|
|
395
451
|
"""
|
|
452
|
+
excluded_paths = self._resolved_exclusion_paths()
|
|
453
|
+
if excluded_paths is None:
|
|
454
|
+
return False
|
|
396
455
|
paths = self._navigable_folder_paths()
|
|
397
456
|
if not paths:
|
|
398
457
|
return False
|
|
399
458
|
norm = os.path.normpath(folder_path)
|
|
400
|
-
|
|
459
|
+
if _path_is_inside_any(norm, excluded_paths):
|
|
460
|
+
return False
|
|
461
|
+
return _path_is_inside_any(norm, paths)
|
|
401
462
|
|
|
402
463
|
def scope(self) -> tuple[list[str], list[str]]:
|
|
403
464
|
"""Return ``(folder_paths, content_ids)`` referenced by the filter.
|
|
@@ -12,6 +12,7 @@ from unique_sdk.cli.config import Config
|
|
|
12
12
|
from unique_sdk.cli.metadata_filter import MetadataFilter
|
|
13
13
|
from unique_sdk.cli.paths import canonical_folder_path, same_folder_path
|
|
14
14
|
from unique_sdk.cli.workspace import manifest_path as workspace_manifest_path
|
|
15
|
+
from unique_sdk.cli.workspace import workspace_root
|
|
15
16
|
|
|
16
17
|
_SEARCH_CONFIG_FILENAME = ".unique-search.json"
|
|
17
18
|
_UPLOADED_CONFIG_FILENAME = ".unique-uploaded.json"
|
|
@@ -20,8 +21,8 @@ _CHAT_FILES_MANIFEST_PATH = Path(".unique") / "chat-files.json"
|
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
def _load_search_config() -> dict[str, Any]:
|
|
23
|
-
"""Load ``.unique-search.json`` from the
|
|
24
|
-
config_path =
|
|
24
|
+
"""Load ``.unique-search.json`` from the workspace root, not the shell cwd."""
|
|
25
|
+
config_path = workspace_root() / _SEARCH_CONFIG_FILENAME
|
|
25
26
|
if not config_path.is_file():
|
|
26
27
|
return {}
|
|
27
28
|
try:
|
|
@@ -32,7 +33,7 @@ def _load_search_config() -> dict[str, Any]:
|
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
def _load_uploaded_config() -> dict[str, Any]:
|
|
35
|
-
"""Load ``.unique-uploaded.json`` from the
|
|
36
|
+
"""Load ``.unique-uploaded.json`` from the workspace root, or ``{}`` when absent or invalid.
|
|
36
37
|
|
|
37
38
|
Written by the Swappable Intelligence runner when the task carries per-row
|
|
38
39
|
uploaded documents (an Agentic Table row's ``selectedUploadedFiles``). It
|
|
@@ -41,7 +42,7 @@ def _load_uploaded_config() -> dict[str, Any]:
|
|
|
41
42
|
the knowledge-base folder scopes, so the scope-bound ``unique-cli search``
|
|
42
43
|
structurally cannot return them. See UN-21780.
|
|
43
44
|
"""
|
|
44
|
-
config_path =
|
|
45
|
+
config_path = workspace_root() / _UPLOADED_CONFIG_FILENAME
|
|
45
46
|
if not config_path.is_file():
|
|
46
47
|
return {}
|
|
47
48
|
try:
|
|
@@ -61,7 +62,7 @@ def _load_mcp_tool_configs() -> dict[str, dict[str, Any]]:
|
|
|
61
62
|
Keyed by the same namespaced tool name the agent passes to ``unique-cli
|
|
62
63
|
mcp``. Returns ``{}`` when absent or invalid.
|
|
63
64
|
"""
|
|
64
|
-
config_path =
|
|
65
|
+
config_path = workspace_root() / _MCP_TOOLS_CONFIG_FILENAME
|
|
65
66
|
if not config_path.is_file():
|
|
66
67
|
return {}
|
|
67
68
|
try:
|
|
@@ -562,6 +563,17 @@ class ShellState:
|
|
|
562
563
|
def prompt(self) -> str:
|
|
563
564
|
return f"{self._path}> "
|
|
564
565
|
|
|
566
|
+
def _require_navigable_folder(self, scope_id: str) -> None:
|
|
567
|
+
"""Refuse a folder the per-message filter excludes. See UN-28458."""
|
|
568
|
+
if self._metadata_filter is None:
|
|
569
|
+
return
|
|
570
|
+
if self.folder_allowed_by_metadata_filter(scope_id):
|
|
571
|
+
return
|
|
572
|
+
raise ValueError(
|
|
573
|
+
"permission denied: target is outside your task scope "
|
|
574
|
+
f"({self.scope_denial_hint()})."
|
|
575
|
+
)
|
|
576
|
+
|
|
565
577
|
def _enter_folder(self, scope_id: str, folder_path: str) -> str:
|
|
566
578
|
"""Store *folder_path* only when *scope_id* is that exact folder."""
|
|
567
579
|
actual = canonical_folder_path(
|
|
@@ -571,6 +583,7 @@ class ShellState:
|
|
|
571
583
|
)
|
|
572
584
|
if not same_folder_path(actual, folder_path):
|
|
573
585
|
raise ValueError(f"cannot change to {folder_path}: resolved to {actual}")
|
|
586
|
+
self._require_navigable_folder(scope_id)
|
|
574
587
|
self._scope_id = scope_id
|
|
575
588
|
self._path = folder_path
|
|
576
589
|
return self._path
|
|
@@ -618,6 +631,7 @@ class ShellState:
|
|
|
618
631
|
company_id=self.config.company_id,
|
|
619
632
|
scope_id=target,
|
|
620
633
|
)
|
|
634
|
+
self._require_navigable_folder(info["id"])
|
|
621
635
|
self._scope_id = info["id"]
|
|
622
636
|
self._path = path_resp["folderPath"]
|
|
623
637
|
return self._path
|
|
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.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/__init__.py
RENAMED
|
File without changes
|
{unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_acronyms.py
RENAMED
|
File without changes
|
{unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_agentic_table.py
RENAMED
|
File without changes
|
{unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_analytics_order.py
RENAMED
|
File without changes
|
{unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_benchmarking.py
RENAMED
|
File without changes
|
{unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_briefing.py
RENAMED
|
File without changes
|
{unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_chat_completion.py
RENAMED
|
File without changes
|
{unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_content.py
RENAMED
|
File without changes
|
{unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_context_memory.py
RENAMED
|
File without changes
|
{unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_elicitation.py
RENAMED
|
File without changes
|
{unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_embedding.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_integrated.py
RENAMED
|
File without changes
|
{unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_llm_models.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_message.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_message_log.py
RENAMED
|
File without changes
|
{unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_message_tool.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_scheduled_task.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_search_string.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/api_resources/_web_search.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/commands/agentic_table.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/commands/cite_file.py
RENAMED
|
File without changes
|
{unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/commands/elicitation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/commands/scheduled_tasks.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/commands/web_search.py
RENAMED
|
File without changes
|
{unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/cli/commands/web_search_config.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
|
|
File without changes
|
{unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/unique_sdk/utils/analytics_order_run.py
RENAMED
|
File without changes
|
{unique_sdk-2026.46.0.dev2 → unique_sdk-2026.46.0.dev4}/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
|