ms-tau-sdk 2.0.2.dev34__tar.gz → 2.0.3.dev35__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.
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/CHANGELOG.md +12 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/PKG-INFO +1 -1
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/pyproject.toml +1 -1
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/runtime/manager.py +8 -1
- ms_tau_sdk-2.0.3.dev35/src/ms_tau_sdk/runtime/secret_entry.py +74 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/runtime/session.py +15 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/tools/mainsequence_mcp.py +80 -10
- ms_tau_sdk-2.0.3.dev35/src/ms_tau_sdk/tools/secret_entry.py +116 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/.gitignore +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/README.md +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/__init__.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/app.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/cli.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/config.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/env_file.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/logs.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/proxy.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/state.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/static/BULMA-LICENSE.txt +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/static/app.js +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/static/board.css +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/static/bulma.min.css +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/static/index.html +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/__init__.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/agent_skills/tau_a2a_runtime_adapter/SKILL.md +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/agent_skills/tau_local_development/SKILL.md +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/agent_skills/tau_project_customization/SKILL.md +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/agent_skills/tau_repository_integration/SKILL.md +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/api/__init__.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/api/a2a.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/api/chat.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/api/conversations.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/api/dependencies.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/api/health.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/api/inspection.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/api/local_chat.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/api/models.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/api/request_identity.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/api/sessions.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/app.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/application.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/backend/__init__.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/backend/assertions.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/backend/auth.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/backend/client.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/backend/local.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/backend/mcp.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/backend/models.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/backend/routes.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/cli.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/errors.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/logging.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/protocols/__init__.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/protocols/a2a_failure.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/protocols/a2a_message.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/protocols/a2a_roles.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/protocols/assistant_ui.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/protocols/chat_history.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/protocols/strict_json.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/providers/__init__.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/providers/definitions.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/providers/factory.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/providers/tau_compat.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/resources/SYSTEM.md +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/resources/__init__.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/resources/loader.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/resources/prompts/review-code-repository.md +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/runtime/__init__.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/runtime/deployment_health.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/runtime/events.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/runtime/extensions.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/runtime/failures.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/runtime/live_turns.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/runtime/observability.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/runtime/provenance.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/runtime/snapshots.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/runtime/task_context.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/sessions/__init__.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/sessions/storage.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/settings.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/skills.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/tools/__init__.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/tools/skill_read.py +0 -0
- {ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/tools/task_control.py +0 -0
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
- Main Sequence MCP no longer offers `agent_session.resolve_runtime_access` to the model. Its
|
|
6
|
+
result carries a short-lived runtime token for direct-runtime clients, and Tau's generic MCP
|
|
7
|
+
projection copied that token into model-visible tool content and into tool details, which reach
|
|
8
|
+
stream events and session history. Tau never needs the token: agent-to-agent turns use
|
|
9
|
+
`a2a.send_message`. The operation is left out of the session's tool list, so
|
|
10
|
+
`mainsequence__agent_session_resolve_runtime_access` is no longer a tool; every other MCP tool
|
|
11
|
+
and its result are unchanged. Direct-runtime clients that call the operation themselves are
|
|
12
|
+
unaffected. See the 2026-10-06 amendment of ADR 0002
|
|
13
|
+
([issue #65](https://github.com/mainsequence-sdk/ms-tau-sdk/issues/65)).
|
|
14
|
+
|
|
3
15
|
## 2.0.2 — 2026-10-06
|
|
4
16
|
|
|
5
17
|
- Runtime credentials require the projected workload identity token file. The runtime credential
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ms-tau-sdk
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.3.dev35
|
|
4
4
|
Summary: Workspace-bound Tau application primitives for Main Sequence projects
|
|
5
5
|
Project-URL: Changelog, https://github.com/mainsequence-sdk/ms-tau-sdk/blob/development/CHANGELOG.md
|
|
6
6
|
Project-URL: Documentation, https://github.com/mainsequence-sdk/ms-tau-sdk/tree/development/docs
|
|
@@ -1116,13 +1116,17 @@ class SessionRuntimeManager:
|
|
|
1116
1116
|
)
|
|
1117
1117
|
cwd = self._resolve_cwd()
|
|
1118
1118
|
project_extension_state = ProjectExtensionState(enabled=True)
|
|
1119
|
-
caller_session_proof = None
|
|
1119
|
+
caller_session_proof: dict[str, JSONValue] | None = None
|
|
1120
1120
|
if not self.settings.local_mode:
|
|
1121
1121
|
caller_session_proof = {
|
|
1122
1122
|
"caller_agent_session_uid": session_uid,
|
|
1123
1123
|
"lease_holder_id": lease.holder_id,
|
|
1124
1124
|
"lease_token": lease.lease_token,
|
|
1125
1125
|
}
|
|
1126
|
+
|
|
1127
|
+
async def record_secret_entry(payload: dict[str, JSONValue]) -> None:
|
|
1128
|
+
await coding_session.append_custom_entry("mainsequence.secret_entry", payload)
|
|
1129
|
+
|
|
1126
1130
|
tools = []
|
|
1127
1131
|
if self.settings.exclude_base_tools:
|
|
1128
1132
|
# Tau lists skills only when a tool named `read` exists. The tool runs only in
|
|
@@ -1134,6 +1138,8 @@ class SessionRuntimeManager:
|
|
|
1134
1138
|
tools.extend(
|
|
1135
1139
|
create_mainsequence_mcp_tools(
|
|
1136
1140
|
mcp_client,
|
|
1141
|
+
session_uid=session_uid,
|
|
1142
|
+
on_secret_entry=record_secret_entry,
|
|
1137
1143
|
caller_session_proof=caller_session_proof,
|
|
1138
1144
|
allow_missing_session_proof=self.settings.local_mode,
|
|
1139
1145
|
)
|
|
@@ -1202,6 +1208,7 @@ class SessionRuntimeManager:
|
|
|
1202
1208
|
provider_name=provider_runtime.name,
|
|
1203
1209
|
model=provider_runtime.model,
|
|
1204
1210
|
mcp_client=mcp_client,
|
|
1211
|
+
secret_entry_proof=caller_session_proof,
|
|
1205
1212
|
runtime_config_sha256=str(session_extra.get("runtime_config_sha256", "")),
|
|
1206
1213
|
provider_control_schema=bootstrap.provider_control.schema_version,
|
|
1207
1214
|
catalog_digest=bootstrap.provider_control.catalog_digest,
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Refresh persisted, reference-only Secret requests before a resumed conversation turn."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from collections.abc import Awaitable, Callable, Sequence
|
|
7
|
+
|
|
8
|
+
from tau_agent.session import CustomEntry, SessionEntry
|
|
9
|
+
from tau_agent.types import JSONValue
|
|
10
|
+
|
|
11
|
+
from ms_tau_sdk.backend.mcp import MainSequenceMCPClient
|
|
12
|
+
from ms_tau_sdk.tools.secret_entry import SecretEntryStatus, secret_entry_result
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async def refresh_secret_entries(
|
|
16
|
+
*,
|
|
17
|
+
entries: Sequence[SessionEntry],
|
|
18
|
+
client: MainSequenceMCPClient,
|
|
19
|
+
private_proof: dict[str, JSONValue] | None,
|
|
20
|
+
record: Callable[[dict[str, JSONValue]], Awaitable[None]],
|
|
21
|
+
) -> str:
|
|
22
|
+
latest: dict[str, SecretEntryStatus] = {}
|
|
23
|
+
for entry in entries:
|
|
24
|
+
if isinstance(entry, CustomEntry) and entry.namespace == "mainsequence.secret_entry":
|
|
25
|
+
# Stored state is not proof of completion. Revalidate pending references below.
|
|
26
|
+
try:
|
|
27
|
+
status = SecretEntryStatus.model_validate_json(json.dumps(entry.data))
|
|
28
|
+
except ValueError:
|
|
29
|
+
raise RuntimeError(
|
|
30
|
+
"Stored Secret entry status is invalid. Check the request before continuing."
|
|
31
|
+
) from None
|
|
32
|
+
latest[str(status.uid)] = status
|
|
33
|
+
checks = []
|
|
34
|
+
for status in latest.values():
|
|
35
|
+
if status.status not in {"pending", "submitting"}:
|
|
36
|
+
continue
|
|
37
|
+
try:
|
|
38
|
+
arguments: dict[str, object] = {
|
|
39
|
+
"request_uid": str(status.uid),
|
|
40
|
+
"organization_environment_uid": str(status.organization_environment_uid),
|
|
41
|
+
}
|
|
42
|
+
result = await client.call_tool(
|
|
43
|
+
"secret_entry.status",
|
|
44
|
+
arguments,
|
|
45
|
+
meta={"mainsequence.ai/caller-session-proof/v1": private_proof}
|
|
46
|
+
if private_proof is not None
|
|
47
|
+
else None,
|
|
48
|
+
)
|
|
49
|
+
projected = secret_entry_result("secret_entry.status", result)
|
|
50
|
+
if not isinstance(projected.details, dict):
|
|
51
|
+
raise ValueError("Unverified status")
|
|
52
|
+
payload = projected.details.get("structured_content")
|
|
53
|
+
if projected.details.get("is_error") or not isinstance(payload, dict):
|
|
54
|
+
raise ValueError("Unverified status")
|
|
55
|
+
if payload.get("uid") != str(status.uid) or payload.get(
|
|
56
|
+
"organization_environment_uid"
|
|
57
|
+
) != str(status.organization_environment_uid):
|
|
58
|
+
raise ValueError("Unbound status")
|
|
59
|
+
except Exception:
|
|
60
|
+
# Do not run the model with an assumed success or copy transport errors to history.
|
|
61
|
+
raise RuntimeError(
|
|
62
|
+
"Secret entry status could not be verified. Retry the conversation after "
|
|
63
|
+
"checking the private page; never send a value in chat."
|
|
64
|
+
) from None
|
|
65
|
+
await record(payload)
|
|
66
|
+
checks.append(projected.text)
|
|
67
|
+
if not checks:
|
|
68
|
+
return ""
|
|
69
|
+
return (
|
|
70
|
+
"\n\nPlatform Secret-entry status checked by the host before this turn:\n"
|
|
71
|
+
+ "\n".join(checks)
|
|
72
|
+
+ "\nContinue dependent work only for confirmed completion. Do not repeat an already "
|
|
73
|
+
"completed business operation or request the secret value."
|
|
74
|
+
)
|
|
@@ -16,6 +16,7 @@ from ms_tau_sdk.backend.models import TauTurnCommit
|
|
|
16
16
|
from ms_tau_sdk.runtime.events import TauRuntimeEvent, translate_tau_event
|
|
17
17
|
from ms_tau_sdk.runtime.extensions import ProjectExtensionState
|
|
18
18
|
from ms_tau_sdk.runtime.provenance import PROVENANCE_NAMESPACE, TurnProvenance
|
|
19
|
+
from ms_tau_sdk.runtime.secret_entry import refresh_secret_entries
|
|
19
20
|
from ms_tau_sdk.sessions.storage import BackendSessionStorage
|
|
20
21
|
|
|
21
22
|
type PlatformEvent = tuple[str, dict[str, JSONValue]]
|
|
@@ -31,6 +32,7 @@ class ActiveSessionRuntime:
|
|
|
31
32
|
provider_name: str = ""
|
|
32
33
|
model: str = ""
|
|
33
34
|
mcp_client: MainSequenceMCPClient | None = None
|
|
35
|
+
secret_entry_proof: dict[str, JSONValue] | None = field(default=None, repr=False)
|
|
34
36
|
lease_renew_task: asyncio.Task[None] | None = None
|
|
35
37
|
persistence_task: asyncio.Task[object] | None = None
|
|
36
38
|
pending_snapshot_commit: TauTurnCommit | None = None
|
|
@@ -91,6 +93,19 @@ class ActiveSessionRuntime:
|
|
|
91
93
|
if platform_event is not None:
|
|
92
94
|
namespace, payload = platform_event
|
|
93
95
|
await self.coding_session.append_custom_entry(namespace, dict(payload))
|
|
96
|
+
if self.mcp_client is not None:
|
|
97
|
+
|
|
98
|
+
async def record_secret_entry(payload: dict[str, JSONValue]) -> None:
|
|
99
|
+
await self.coding_session.append_custom_entry(
|
|
100
|
+
"mainsequence.secret_entry", payload
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
content += await refresh_secret_entries(
|
|
104
|
+
entries=await self.storage.read_all(),
|
|
105
|
+
client=self.mcp_client,
|
|
106
|
+
private_proof=self.secret_entry_proof,
|
|
107
|
+
record=record_secret_entry,
|
|
108
|
+
)
|
|
94
109
|
async for event in self.coding_session.prompt(content):
|
|
95
110
|
translated = translate_tau_event(event)
|
|
96
111
|
if settled_event is not None and translated.type != "agent_settled":
|
|
@@ -4,9 +4,10 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
6
|
import re
|
|
7
|
-
from collections.abc import Mapping
|
|
7
|
+
from collections.abc import Awaitable, Callable, Mapping
|
|
8
8
|
from copy import deepcopy
|
|
9
9
|
from typing import Literal, cast
|
|
10
|
+
from uuid import NAMESPACE_URL, uuid5
|
|
10
11
|
|
|
11
12
|
from mcp import types
|
|
12
13
|
from tau_agent.messages import ImageContent, TextContent
|
|
@@ -23,12 +24,17 @@ from ms_tau_sdk.backend.mcp import (
|
|
|
23
24
|
ENVIRONMENT_UID_ARGUMENT,
|
|
24
25
|
MainSequenceMCPClient,
|
|
25
26
|
)
|
|
27
|
+
from ms_tau_sdk.tools.secret_entry import secret_entry_result
|
|
26
28
|
|
|
27
29
|
_INVALID_TOOL_NAME = re.compile(r"[^A-Za-z0-9_-]")
|
|
28
30
|
_RESOURCE_TOOL_NAME = "mainsequence__read_resource"
|
|
29
31
|
CALLER_SESSION_PROOF_META_KEY = "mainsequence.ai/caller-session-proof/v1"
|
|
30
32
|
CALLER_SESSION_PROOF_REQUIRED_META_KEY = "mainsequence.ai/requires-caller-session-proof/v1"
|
|
31
33
|
A2A_SEND_TOOL = "a2a.send_message"
|
|
34
|
+
# These operations return credentials for direct-runtime clients. Tau never calls them for
|
|
35
|
+
# the model: agent-to-agent turns use a2a.send_message, so the credential stays out of model
|
|
36
|
+
# content, tool details, events, and persisted history.
|
|
37
|
+
CREDENTIAL_RESULT_MCP_TOOLS = frozenset({"agent_session.resolve_runtime_access"})
|
|
32
38
|
|
|
33
39
|
|
|
34
40
|
def _tau_tool_name(mcp_name: str) -> str:
|
|
@@ -51,6 +57,8 @@ def _tool_result(
|
|
|
51
57
|
canonical_name: str,
|
|
52
58
|
result: types.CallToolResult,
|
|
53
59
|
) -> AgentToolResult:
|
|
60
|
+
if canonical_name in {"secret_entry.start", "secret_entry.status", "secret_entry.cancel"}:
|
|
61
|
+
return secret_entry_result(canonical_name, result)
|
|
54
62
|
content: list[TextContent | ImageContent] = []
|
|
55
63
|
for block in result.content:
|
|
56
64
|
if isinstance(block, types.TextContent):
|
|
@@ -87,6 +95,11 @@ def _tau_tool_input_schema(
|
|
|
87
95
|
local_user_semantics: bool = False,
|
|
88
96
|
) -> Mapping[str, JSONValue]:
|
|
89
97
|
schema = deepcopy(tool.inputSchema)
|
|
98
|
+
if tool.name == "secret_entry.start":
|
|
99
|
+
schema.get("properties", {}).pop("idempotency_key", None)
|
|
100
|
+
schema["required"] = [
|
|
101
|
+
name for name in schema.get("required", []) if name != "idempotency_key"
|
|
102
|
+
]
|
|
90
103
|
if tool.name == A2A_SEND_TOOL:
|
|
91
104
|
properties = schema.setdefault("properties", {})
|
|
92
105
|
if isinstance(properties, dict):
|
|
@@ -160,6 +173,8 @@ def _create_mcp_tool(
|
|
|
160
173
|
tau_name: str,
|
|
161
174
|
private_meta: Mapping[str, JSONValue] | None = None,
|
|
162
175
|
local_user_semantics: bool = False,
|
|
176
|
+
session_uid: str | None = None,
|
|
177
|
+
on_secret_entry: Callable[[dict[str, JSONValue]], Awaitable[None]] | None = None,
|
|
163
178
|
) -> AgentTool:
|
|
164
179
|
canonical_name = tool.name
|
|
165
180
|
annotations = tool.annotations
|
|
@@ -177,7 +192,36 @@ def _create_mcp_tool(
|
|
|
177
192
|
signal: ToolCancellationToken | None = None,
|
|
178
193
|
on_update: ToolUpdateCallback | None = None,
|
|
179
194
|
) -> AgentToolResult:
|
|
180
|
-
del
|
|
195
|
+
del on_update
|
|
196
|
+
if canonical_name in {"secret_entry.start", "secret_entry.status", "secret_entry.cancel"}:
|
|
197
|
+
allowed = {"organization_environment_uid", "request_uid"}
|
|
198
|
+
if canonical_name == "secret_entry.start":
|
|
199
|
+
allowed = {
|
|
200
|
+
"organization_environment_uid",
|
|
201
|
+
"name",
|
|
202
|
+
"secret_uid",
|
|
203
|
+
"purpose",
|
|
204
|
+
"consumer_user_uid",
|
|
205
|
+
}
|
|
206
|
+
if set(arguments) - allowed:
|
|
207
|
+
return AgentToolResult(
|
|
208
|
+
content=[
|
|
209
|
+
TextContent(
|
|
210
|
+
text=(
|
|
211
|
+
"Secret-entry tools accept only metadata and references. "
|
|
212
|
+
"Enter values on the private page."
|
|
213
|
+
)
|
|
214
|
+
)
|
|
215
|
+
],
|
|
216
|
+
details={"mcp_tool": canonical_name, "is_error": True},
|
|
217
|
+
)
|
|
218
|
+
if canonical_name == "secret_entry.start":
|
|
219
|
+
arguments = {
|
|
220
|
+
**arguments,
|
|
221
|
+
"idempotency_key": str(
|
|
222
|
+
uuid5(NAMESPACE_URL, f"ms-tau-secret-entry:{session_uid}:{tool_call_id}")
|
|
223
|
+
),
|
|
224
|
+
}
|
|
181
225
|
if signal is not None and signal.is_cancelled():
|
|
182
226
|
return AgentToolResult(
|
|
183
227
|
content=[TextContent(text="Main Sequence MCP tool call was cancelled.")],
|
|
@@ -199,15 +243,35 @@ def _create_mcp_tool(
|
|
|
199
243
|
raise ValueError("Local A2A Task communication requires completion_policy 'poll'")
|
|
200
244
|
if response_kind == "message" and completion_policy is not None:
|
|
201
245
|
raise ValueError("completion_policy is not valid for Message response_kind")
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
246
|
+
try:
|
|
247
|
+
if private_meta is None:
|
|
248
|
+
result = await client.call_tool(canonical_name, dict(arguments))
|
|
249
|
+
else:
|
|
250
|
+
result = await client.call_tool(
|
|
251
|
+
canonical_name,
|
|
252
|
+
dict(arguments),
|
|
253
|
+
meta=dict(private_meta),
|
|
254
|
+
)
|
|
255
|
+
except Exception:
|
|
256
|
+
if canonical_name not in {
|
|
257
|
+
"secret_entry.start",
|
|
258
|
+
"secret_entry.status",
|
|
259
|
+
"secret_entry.cancel",
|
|
260
|
+
}:
|
|
261
|
+
raise
|
|
262
|
+
return secret_entry_result(
|
|
263
|
+
canonical_name, types.CallToolResult(content=[], isError=True)
|
|
209
264
|
)
|
|
210
|
-
|
|
265
|
+
projected = _tool_result(canonical_name=canonical_name, result=result)
|
|
266
|
+
if (
|
|
267
|
+
canonical_name.startswith("secret_entry.")
|
|
268
|
+
and on_secret_entry is not None
|
|
269
|
+
and isinstance(projected.details, dict)
|
|
270
|
+
):
|
|
271
|
+
payload = projected.details.get("structured_content")
|
|
272
|
+
if not projected.details.get("is_error") and isinstance(payload, dict):
|
|
273
|
+
await on_secret_entry(payload)
|
|
274
|
+
return projected
|
|
211
275
|
|
|
212
276
|
return AgentTool(
|
|
213
277
|
name=tau_name,
|
|
@@ -285,10 +349,14 @@ def create_mainsequence_mcp_tools(
|
|
|
285
349
|
*,
|
|
286
350
|
caller_session_proof: Mapping[str, JSONValue] | None = None,
|
|
287
351
|
allow_missing_session_proof: bool = False,
|
|
352
|
+
session_uid: str | None = None,
|
|
353
|
+
on_secret_entry: Callable[[dict[str, JSONValue]], Awaitable[None]] | None = None,
|
|
288
354
|
) -> list[AgentTool]:
|
|
289
355
|
tools: list[AgentTool] = []
|
|
290
356
|
names: set[str] = set()
|
|
291
357
|
for tool in client.tools:
|
|
358
|
+
if tool.name in CREDENTIAL_RESULT_MCP_TOOLS:
|
|
359
|
+
continue
|
|
292
360
|
tau_name = _tau_tool_name(tool.name)
|
|
293
361
|
if tau_name in names or tau_name == _RESOURCE_TOOL_NAME:
|
|
294
362
|
raise ValueError(f"Main Sequence MCP tool name collision: {tool.name}")
|
|
@@ -304,6 +372,8 @@ def create_mainsequence_mcp_tools(
|
|
|
304
372
|
allow_missing_proof=allow_missing_session_proof,
|
|
305
373
|
),
|
|
306
374
|
local_user_semantics=allow_missing_session_proof,
|
|
375
|
+
session_uid=session_uid,
|
|
376
|
+
on_secret_entry=on_secret_entry,
|
|
307
377
|
)
|
|
308
378
|
)
|
|
309
379
|
if client.resources:
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Reference-only Secret entry. Values travel directly from a private browser to Django."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import Literal
|
|
8
|
+
from urllib.parse import parse_qs, urlsplit
|
|
9
|
+
from uuid import UUID
|
|
10
|
+
|
|
11
|
+
from mcp import types
|
|
12
|
+
from pydantic import BaseModel, ConfigDict, ValidationError, model_validator
|
|
13
|
+
from tau_agent.messages import TextContent
|
|
14
|
+
from tau_agent.tools import AgentToolResult
|
|
15
|
+
|
|
16
|
+
from ms_tau_sdk.runtime.task_context import active_task_execution
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class SecretEntryStatus(BaseModel):
|
|
20
|
+
model_config = ConfigDict(extra="forbid", strict=True)
|
|
21
|
+
|
|
22
|
+
uid: UUID
|
|
23
|
+
status: Literal["pending", "submitting", "completed", "cancelled", "expired", "failed"]
|
|
24
|
+
operation: Literal["create", "rotate"]
|
|
25
|
+
name: str
|
|
26
|
+
purpose: str
|
|
27
|
+
organization_environment_uid: UUID
|
|
28
|
+
organization_environment_name: str
|
|
29
|
+
requesting_agent_name: str | None
|
|
30
|
+
consumer_user_uid: UUID | None
|
|
31
|
+
secret_uid: UUID | None
|
|
32
|
+
entry_url: str
|
|
33
|
+
expires_at: datetime
|
|
34
|
+
error_code: str
|
|
35
|
+
requirement_reference: str
|
|
36
|
+
|
|
37
|
+
@model_validator(mode="after")
|
|
38
|
+
def trusted_entry_link(self) -> SecretEntryStatus:
|
|
39
|
+
url = urlsplit(self.entry_url)
|
|
40
|
+
local = url.scheme == "http" and url.hostname in {"localhost", "127.0.0.1"}
|
|
41
|
+
if (
|
|
42
|
+
(url.scheme != "https" and not local)
|
|
43
|
+
or not url.hostname
|
|
44
|
+
or url.username
|
|
45
|
+
or url.password
|
|
46
|
+
or url.fragment
|
|
47
|
+
or url.path != "/app/main-sequence-foundry/secrets"
|
|
48
|
+
or parse_qs(url.query)
|
|
49
|
+
!= {
|
|
50
|
+
"secretEntryRequest": [str(self.uid)],
|
|
51
|
+
"organization_environment_uid": [str(self.organization_environment_uid)],
|
|
52
|
+
}
|
|
53
|
+
or self.requirement_reference != f"secret-entry:{self.uid}"
|
|
54
|
+
or (self.status != "completed" and self.secret_uid is not None)
|
|
55
|
+
):
|
|
56
|
+
raise ValueError("Invalid Secret-entry binding")
|
|
57
|
+
return self
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def secret_entry_result(canonical_name: str, result: types.CallToolResult) -> AgentToolResult:
|
|
61
|
+
"""Ignore free-form MCP content; accept only the complete value-free status contract."""
|
|
62
|
+
try:
|
|
63
|
+
if result.isError or result.structuredContent is None:
|
|
64
|
+
raise ValueError("Secret entry is unavailable")
|
|
65
|
+
# Strict JSON accepts UUID/date strings but rejects coercion and extra fields.
|
|
66
|
+
status = SecretEntryStatus.model_validate_json(json.dumps(result.structuredContent))
|
|
67
|
+
except (ValidationError, ValueError, TypeError):
|
|
68
|
+
return AgentToolResult(
|
|
69
|
+
content=[
|
|
70
|
+
TextContent(
|
|
71
|
+
text=(
|
|
72
|
+
"Secret entry could not be verified. Check access or retry; "
|
|
73
|
+
"never send a value in chat."
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
],
|
|
77
|
+
details={"mcp_tool": canonical_name, "is_error": True},
|
|
78
|
+
)
|
|
79
|
+
payload = status.model_dump(mode="json")
|
|
80
|
+
if status.status == "pending":
|
|
81
|
+
guidance = (
|
|
82
|
+
f"Enter the value on the authenticated private page: {status.entry_url}\n"
|
|
83
|
+
"Open it yourself outside agent browser control or screen capture. "
|
|
84
|
+
"Do not paste the value here. "
|
|
85
|
+
"Wait for the person, then call secret_entry.status; "
|
|
86
|
+
"opening the page does not complete the request."
|
|
87
|
+
)
|
|
88
|
+
elif status.status == "submitting":
|
|
89
|
+
guidance = (
|
|
90
|
+
"Storage is in progress. Check secret_entry.status; do not repeat the submission."
|
|
91
|
+
)
|
|
92
|
+
elif status.status == "completed":
|
|
93
|
+
guidance = "Django confirmed storage. Saving does not grant access to a consumer."
|
|
94
|
+
elif status.error_code == "storage_outcome_unknown":
|
|
95
|
+
guidance = (
|
|
96
|
+
"The storage outcome is uncertain. Do not retry the value write; "
|
|
97
|
+
"ask an operator to reconcile this request."
|
|
98
|
+
)
|
|
99
|
+
else:
|
|
100
|
+
guidance = f"Secret entry is {status.status}. No completion is confirmed."
|
|
101
|
+
task = active_task_execution()
|
|
102
|
+
if (
|
|
103
|
+
canonical_name == "secret_entry.start"
|
|
104
|
+
and status.status == "pending"
|
|
105
|
+
and task is not None
|
|
106
|
+
and task.interruption_status is None
|
|
107
|
+
):
|
|
108
|
+
task.request_interruption(
|
|
109
|
+
status="auth_required",
|
|
110
|
+
text=guidance,
|
|
111
|
+
details={"requirementReference": status.requirement_reference},
|
|
112
|
+
)
|
|
113
|
+
return AgentToolResult(
|
|
114
|
+
content=[TextContent(text=guidance), TextContent(text=json.dumps(payload, sort_keys=True))],
|
|
115
|
+
details={"mcp_tool": canonical_name, "is_error": False, "structured_content": payload},
|
|
116
|
+
)
|
|
File without changes
|
|
File without changes
|
{ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/__init__.py
RENAMED
|
File without changes
|
{ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/app.py
RENAMED
|
File without changes
|
{ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/cli.py
RENAMED
|
File without changes
|
{ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/config.py
RENAMED
|
File without changes
|
{ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/env_file.py
RENAMED
|
File without changes
|
{ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/logs.py
RENAMED
|
File without changes
|
{ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/proxy.py
RENAMED
|
File without changes
|
{ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/state.py
RENAMED
|
File without changes
|
|
File without changes
|
{ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/packages/tau-board/src/ms_tau_board/static/app.js
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
|
|
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
|
|
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
|
|
File without changes
|
{ms_tau_sdk-2.0.2.dev34 → ms_tau_sdk-2.0.3.dev35}/src/ms_tau_sdk/runtime/deployment_health.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
|