mainsequence 9.0.1.dev30__tar.gz → 9.0.2.dev32__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.
- {mainsequence-9.0.1.dev30/mainsequence.egg-info → mainsequence-9.0.2.dev32}/PKG-INFO +1 -1
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/agent_scaffold/AGENTS.md +6 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/agent_scaffold/skills/maintenance/code_repository_maintenance/SKILL.md +21 -6
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/cli/cli.py +108 -95
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/cli/config.py +18 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/client/utils.py +149 -37
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/code_repository_skills.py +51 -200
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/scaffold_skills.py +54 -18
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32/mainsequence.egg-info}/PKG-INFO +1 -1
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/pyproject.toml +1 -1
- mainsequence-9.0.2.dev32/tests/test_auth_bootstrap.py +339 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/tests/test_cli.py +110 -26
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/tests/test_code_repository_skills.py +22 -157
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/tests/test_scaffold_skills.py +58 -0
- mainsequence-9.0.1.dev30/tests/test_auth_bootstrap.py +0 -182
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/LICENSE +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/README.md +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/agent_scaffold/skills/a2a_sdk_execution/SKILL.md +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/agent_scaffold/skills/application_surfaces/api_surfaces/SKILL.md +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/agent_scaffold/skills/maintenance/bug_auditor/SKILL.md +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/agent_scaffold/skills/maintenance/metatables_transition/SKILL.md +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/agent_scaffold/skills/platform_operations/access_control_and_sharing/SKILL.md +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/agent_scaffold/skills/platform_operations/orchestration_and_releases/SKILL.md +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/agent_scaffold/skills/sdk_code_repository_execution/SKILL.md +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/__init__.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/__main__.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/_request_identity.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/bootstrap.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/cli/__init__.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/cli/api.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/cli/browser_auth.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/cli/code_repository_status.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/cli/docker_utils.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/cli/doctor.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/cli/local_ops.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/cli/model_filters.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/cli/pydantic_cli.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/cli/sdk_utils.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/cli/ssh_utils.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/cli/ui.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/client/__init__.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/client/agent_runtime_models.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/client/base.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/client/client.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/client/compute_validation.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/client/exceptions.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/client/github_issues.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/client/models_foundry.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/client/models_helpers.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/client/models_user.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/client/observability.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/client/value_sets.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/code_repository_context.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/defaults.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/instrumentation/__init__.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/instrumentation/utils.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/logconf.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/repository_identity_security.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/runtime_flags.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/server/__init__.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/server/caller_assertions.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence/server/fastapi.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence.egg-info/SOURCES.txt +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence.egg-info/dependency_links.txt +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence.egg-info/entry_points.txt +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence.egg-info/requires.txt +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/mainsequence.egg-info/top_level.txt +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/setup.cfg +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/tests/test_auth_precedence.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/tests/test_caller_assertions.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/tests/test_canonical_api_route_contract.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/tests/test_cli_auth_contract.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/tests/test_cli_browser_auth.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/tests/test_cli_secure_storage.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/tests/test_client.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/tests/test_code_repository_branch_contract.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/tests/test_code_repository_documentation_contract.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/tests/test_code_repository_runtime_context.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/tests/test_dependency_extras.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/tests/test_dev_release_version.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/tests/test_environment_resource_context.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/tests/test_environment_response_contracts.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/tests/test_fastapi_request_identity.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/tests/test_filter_normalization.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/tests/test_github_issues_client.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/tests/test_independent_context.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/tests/test_instrumentation.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/tests/test_job_client.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/tests/test_local_ops.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/tests/test_logconf.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/tests/test_models_user_request_bound_auth.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/tests/test_notification_uid_contract.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/tests/test_observability_client.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/tests/test_secret_client_model.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/tests/test_source_ownership.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/tests/test_ssh_utils.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/tests/test_tolerant_response_reading.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/tests/test_tolerant_value_sets.py +0 -0
- {mainsequence-9.0.1.dev30 → mainsequence-9.0.2.dev32}/tests/test_user_application_facts.py +0 -0
|
@@ -42,6 +42,7 @@ specific update:
|
|
|
42
42
|
|
|
43
43
|
- `mainsequence code-repository update-sdk --path .`
|
|
44
44
|
- `mainsequence code-repository update-agent-skills --path .`
|
|
45
|
+
- `mainsequence code-repository update-platform-skills --path .`
|
|
45
46
|
- `mainsequence code-repository update AGENTS.md --path .`
|
|
46
47
|
- `uv run ms-tau skills sync --path .`
|
|
47
48
|
|
|
@@ -51,6 +52,11 @@ to report, not permission to mutate the repository. The same rule applies to
|
|
|
51
52
|
requested, use the `code_repository_maintenance` skill. The `mainsequence` CLI
|
|
52
53
|
never owns or updates the `ms_tau_sdk` namespace.
|
|
53
54
|
|
|
55
|
+
The SDK owns all of `.agents/skills/mainsequence/`. Its skill copy is local,
|
|
56
|
+
requires no sign-in, and deletes everything absent from the installed SDK
|
|
57
|
+
bundle. Platform skills are installed separately under
|
|
58
|
+
`.agents/skills/mainsequence_platform/` by the authenticated platform update.
|
|
59
|
+
|
|
54
60
|
## Route By Task
|
|
55
61
|
|
|
56
62
|
- Product architecture, platform ontology, and CodeRepository Blueprint work:
|
|
@@ -172,22 +172,37 @@ to publish the CodeRepository changes.
|
|
|
172
172
|
## Refresh Managed Skills And Instructions
|
|
173
173
|
|
|
174
174
|
Run either managed-scaffold command only when the user explicitly requests that
|
|
175
|
-
specific update. When both are requested, refresh the SDK-owned
|
|
176
|
-
|
|
175
|
+
specific update. When both are requested, refresh the SDK-owned skills first,
|
|
176
|
+
then update the managed Main Sequence block in
|
|
177
177
|
`AGENTS.md`:
|
|
178
178
|
|
|
179
179
|
```bash
|
|
180
|
-
mainsequence code-repository update-agent-skills --path .
|
|
181
|
-
mainsequence code-repository update AGENTS.md --path .
|
|
180
|
+
uv run --offline mainsequence code-repository update-agent-skills --path .
|
|
181
|
+
uv run --offline mainsequence code-repository update AGENTS.md --path .
|
|
182
182
|
```
|
|
183
183
|
|
|
184
184
|
Verify `.agents/skills/mainsequence/PINNED_FROM.txt` after success:
|
|
185
185
|
|
|
186
|
-
- `
|
|
187
|
-
-
|
|
186
|
+
- `pinned_version` must match the installed CodeRepository SDK;
|
|
187
|
+
- the namespace must contain exactly the installed SDK's skill folders;
|
|
188
188
|
- repository-owned skills outside `.agents/skills/mainsequence/` must remain
|
|
189
189
|
untouched.
|
|
190
190
|
|
|
191
|
+
The SDK owns the whole `mainsequence` namespace. Copying it requires no login
|
|
192
|
+
or platform access and deletes obsolete folders, including retired table
|
|
193
|
+
workflow skills. Do not preserve old or platform-owned content in that folder.
|
|
194
|
+
The installed `metatables` package owns table workflows in its own namespace.
|
|
195
|
+
|
|
196
|
+
When the user requests platform skills, use the separate authenticated command:
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
mainsequence code-repository update-platform-skills --path .
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
It owns only `.agents/skills/mainsequence_platform/` and records the platform
|
|
203
|
+
manifest, ontology, and resource hashes there. A backend or authentication
|
|
204
|
+
failure in this command must not block a local SDK skill copy.
|
|
205
|
+
|
|
191
206
|
The skill update is staged and atomic. If it fails, report the failing lane and
|
|
192
207
|
preserve the previous valid managed tree. Because this operation can update
|
|
193
208
|
this skill, reload the refreshed `code_repository_maintenance/SKILL.md` before starting
|
|
@@ -56,8 +56,9 @@ from ..code_repository_context import (
|
|
|
56
56
|
)
|
|
57
57
|
from ..code_repository_skills import (
|
|
58
58
|
CodeRepositorySkillAssemblyError,
|
|
59
|
-
|
|
59
|
+
install_platform_code_repository_skills,
|
|
60
60
|
)
|
|
61
|
+
from ..scaffold_skills import ScaffoldSkillCopyBlocked, copy_scaffold_skills
|
|
61
62
|
from . import config as cfg
|
|
62
63
|
from .api import (
|
|
63
64
|
ApiError,
|
|
@@ -7845,6 +7846,33 @@ def code_repository_update_sdk(
|
|
|
7845
7846
|
run_uv(uv, ["sync"], cwd=code_repository_dir)
|
|
7846
7847
|
|
|
7847
7848
|
success("SDK update complete.")
|
|
7849
|
+
_warn_if_sdk_skills_stale(code_repository_dir)
|
|
7850
|
+
|
|
7851
|
+
|
|
7852
|
+
def _warn_if_sdk_skills_stale(code_repository_dir: pathlib.Path) -> None:
|
|
7853
|
+
try:
|
|
7854
|
+
version = _code_repository_installed_package_version(code_repository_dir, "mainsequence")
|
|
7855
|
+
sentinel = code_repository_dir / ".agents" / "skills" / "mainsequence" / "PINNED_FROM.txt"
|
|
7856
|
+
values = (
|
|
7857
|
+
dict(
|
|
7858
|
+
line.split("=", 1)
|
|
7859
|
+
for line in sentinel.read_text(encoding="utf-8").splitlines()
|
|
7860
|
+
if "=" in line
|
|
7861
|
+
)
|
|
7862
|
+
if sentinel.is_file()
|
|
7863
|
+
else {}
|
|
7864
|
+
)
|
|
7865
|
+
except (OSError, ValueError, typer.Exit):
|
|
7866
|
+
warn("Could not check the vendored SDK skill pin. Run update-agent-skills to refresh it.")
|
|
7867
|
+
return
|
|
7868
|
+
pinned = values.get("pinned_version", values.get("sdk_version"))
|
|
7869
|
+
if pinned != version:
|
|
7870
|
+
warn(
|
|
7871
|
+
f"Vendored SDK skills are stale or missing (pinned: {pinned or 'missing'}; installed: {version}). "
|
|
7872
|
+
"No scaffold files were changed. Refresh with "
|
|
7873
|
+
"`uv run --offline mainsequence code-repository update-agent-skills --path .` "
|
|
7874
|
+
"and `uv run --offline mainsequence code-repository update AGENTS.md --path .`."
|
|
7875
|
+
)
|
|
7848
7876
|
|
|
7849
7877
|
|
|
7850
7878
|
@code_repository.command("update")
|
|
@@ -7935,124 +7963,61 @@ def code_repository_update_agent_skills(
|
|
|
7935
7963
|
path: str | None = typer.Option(None, "--path", help="CodeRepository directory"),
|
|
7936
7964
|
):
|
|
7937
7965
|
"""
|
|
7938
|
-
|
|
7966
|
+
Replace SDK-owned skills with the target checkout's installed SDK bundle.
|
|
7939
7967
|
|
|
7940
|
-
|
|
7941
|
-
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
|
|
7945
|
-
such as `AGENTS.md` are not copied by this command.
|
|
7968
|
+
No sign-in or backend access is required unless a CodeRepository UID
|
|
7969
|
+
assertion is supplied. The SDK owns all of .agents/skills/mainsequence:
|
|
7970
|
+
files and folders absent from the installed bundle are deleted. Other
|
|
7971
|
+
namespaces are untouched. AGENTS.md is refreshed separately with
|
|
7972
|
+
code-repository update AGENTS.md. Platform skills use update-platform-skills.
|
|
7946
7973
|
|
|
7947
7974
|
Examples
|
|
7948
7975
|
--------
|
|
7949
|
-
```bash
|
|
7950
|
-
mainsequence code-repository update-agent-skills
|
|
7951
7976
|
mainsequence code-repository update-agent-skills --path .
|
|
7952
|
-
mainsequence code-repository update-agent-skills --code-repository-uid code-repository-uid-123
|
|
7953
|
-
```
|
|
7954
7977
|
"""
|
|
7955
|
-
|
|
7956
|
-
|
|
7957
|
-
|
|
7958
|
-
|
|
7959
|
-
if not skills_dir.exists() or not skills_dir.is_dir():
|
|
7960
|
-
error(f"CodeRepository-installed agent_scaffold bundle is missing skills/: {skills_dir}")
|
|
7961
|
-
raise typer.Exit(1)
|
|
7962
|
-
|
|
7963
|
-
pinned_version = _code_repository_installed_package_version(code_repository_dir, "mainsequence")
|
|
7964
|
-
source_checkout_root = _mainsequence_source_checkout_root()
|
|
7965
|
-
protected_code_repository_roots = (
|
|
7966
|
-
(source_checkout_root,) if source_checkout_root is not None else ()
|
|
7967
|
-
)
|
|
7978
|
+
directory = _resolve_code_repository_dir(code_repository_id, path)
|
|
7979
|
+
skills_dir = _code_repository_agent_scaffold_bundle_dir(directory) / "skills"
|
|
7980
|
+
version = _code_repository_installed_package_version(directory, "mainsequence")
|
|
7981
|
+
source_root = _mainsequence_source_checkout_root()
|
|
7968
7982
|
try:
|
|
7969
|
-
|
|
7970
|
-
|
|
7971
|
-
|
|
7972
|
-
sdk_library_name="mainsequence",
|
|
7983
|
+
result = copy_scaffold_skills(
|
|
7984
|
+
code_repository_dir=directory,
|
|
7985
|
+
library_name="mainsequence",
|
|
7973
7986
|
namespace="mainsequence",
|
|
7974
|
-
|
|
7975
|
-
|
|
7976
|
-
platform_catalog=platform_catalog,
|
|
7987
|
+
skills_path=skills_dir,
|
|
7988
|
+
pinned_version=version,
|
|
7977
7989
|
command="mainsequence code-repository update-agent-skills",
|
|
7978
|
-
protected_code_repository_roots=
|
|
7990
|
+
protected_code_repository_roots=(source_root,) if source_root else (),
|
|
7979
7991
|
)
|
|
7980
|
-
except (
|
|
7981
|
-
ApiError,
|
|
7982
|
-
CodeRepositorySkillAssemblyError,
|
|
7983
|
-
FileNotFoundError,
|
|
7984
|
-
OSError,
|
|
7985
|
-
ValueError,
|
|
7986
|
-
) as exc:
|
|
7992
|
+
except (ScaffoldSkillCopyBlocked, FileNotFoundError, OSError, ValueError) as exc:
|
|
7987
7993
|
error(str(exc))
|
|
7988
7994
|
raise typer.Exit(1) from exc
|
|
7989
|
-
|
|
7990
7995
|
updated = [
|
|
7991
|
-
{
|
|
7992
|
-
|
|
7993
|
-
"owner": item.owner,
|
|
7994
|
-
"source": item.source,
|
|
7995
|
-
"destination": item.destination,
|
|
7996
|
-
"content_sha256": item.content_sha256,
|
|
7997
|
-
}
|
|
7998
|
-
for item in install_result.installed
|
|
7996
|
+
{"name": item.name, "owner": "sdk", "source": item.source, "destination": item.destination}
|
|
7997
|
+
for item in result.copied
|
|
7999
7998
|
]
|
|
8000
|
-
|
|
8001
7999
|
payload = {
|
|
8002
|
-
"code_repository":
|
|
8003
|
-
"library_name":
|
|
8004
|
-
"namespace":
|
|
8005
|
-
"skills_path":
|
|
8006
|
-
"destination_root":
|
|
8007
|
-
"sentinel_path":
|
|
8008
|
-
"pinned_version":
|
|
8000
|
+
"code_repository": directory,
|
|
8001
|
+
"library_name": result.library_name,
|
|
8002
|
+
"namespace": result.namespace,
|
|
8003
|
+
"skills_path": result.skills_path,
|
|
8004
|
+
"destination_root": result.destination_root,
|
|
8005
|
+
"sentinel_path": result.sentinel_path,
|
|
8006
|
+
"pinned_version": result.pinned_version,
|
|
8009
8007
|
"sdk": {
|
|
8010
|
-
"library_name":
|
|
8011
|
-
"version":
|
|
8012
|
-
"skills_path":
|
|
8013
|
-
},
|
|
8014
|
-
"platform": {
|
|
8015
|
-
"source_url": platform_catalog.source_url,
|
|
8016
|
-
"manifest_version": platform_catalog.manifest_version,
|
|
8017
|
-
"manifest_sha256": platform_catalog.manifest_sha256,
|
|
8018
|
-
"ontology_uri": platform_catalog.ontology_uri,
|
|
8019
|
-
"ontology_sha256": platform_catalog.ontology_sha256,
|
|
8020
|
-
"resources": [
|
|
8021
|
-
{
|
|
8022
|
-
"name": resource.name,
|
|
8023
|
-
"uri": resource.uri,
|
|
8024
|
-
"path": str(resource.resource_path),
|
|
8025
|
-
"content_sha256": resource.content_sha256,
|
|
8026
|
-
}
|
|
8027
|
-
for resource in platform_catalog.resources
|
|
8028
|
-
],
|
|
8029
|
-
"skills": [
|
|
8030
|
-
{
|
|
8031
|
-
"name": skill.name,
|
|
8032
|
-
"uri": skill.uri,
|
|
8033
|
-
"path": str(skill.relative_path),
|
|
8034
|
-
"content_sha256": skill.content_sha256,
|
|
8035
|
-
}
|
|
8036
|
-
for skill in platform_catalog.skills
|
|
8037
|
-
],
|
|
8008
|
+
"library_name": result.library_name,
|
|
8009
|
+
"version": version,
|
|
8010
|
+
"skills_path": result.skills_path,
|
|
8038
8011
|
},
|
|
8039
8012
|
"updated_count": len(updated),
|
|
8040
8013
|
"updated": updated,
|
|
8041
8014
|
}
|
|
8042
8015
|
if _emit_json(payload):
|
|
8043
8016
|
return
|
|
8044
|
-
|
|
8045
|
-
success("Updated .agents/skills/mainsequence from installed SDK and platform sources.")
|
|
8017
|
+
success("Replaced SDK-owned .agents/skills/mainsequence with the installed bundle.")
|
|
8046
8018
|
print_kv(
|
|
8047
8019
|
"CodeRepository Skill Provenance",
|
|
8048
|
-
[
|
|
8049
|
-
("SDK Library", install_result.sdk_library_name),
|
|
8050
|
-
("SDK Version", install_result.sdk_version),
|
|
8051
|
-
("Platform Manifest", platform_catalog.manifest_sha256),
|
|
8052
|
-
("Platform Resources", len(platform_catalog.resources)),
|
|
8053
|
-
("Platform Skills", len(platform_catalog.skills)),
|
|
8054
|
-
("Sentinel", str(install_result.sentinel_path)),
|
|
8055
|
-
],
|
|
8020
|
+
[("SDK Version", version), ("Sentinel", str(result.sentinel_path))],
|
|
8056
8021
|
)
|
|
8057
8022
|
print_table(
|
|
8058
8023
|
"Updated CodeRepository Skills",
|
|
@@ -8061,6 +8026,54 @@ def code_repository_update_agent_skills(
|
|
|
8061
8026
|
)
|
|
8062
8027
|
|
|
8063
8028
|
|
|
8029
|
+
@code_repository.command("update-platform-skills")
|
|
8030
|
+
def code_repository_update_platform_skills(
|
|
8031
|
+
path: str | None = typer.Option(None, "--path", help="CodeRepository directory"),
|
|
8032
|
+
):
|
|
8033
|
+
"""Refresh authenticated platform skills in .agents/skills/mainsequence_platform.
|
|
8034
|
+
|
|
8035
|
+
This requires a signed-in session and a reachable backend. The SDK-owned
|
|
8036
|
+
mainsequence namespace and other library namespaces remain untouched.
|
|
8037
|
+
"""
|
|
8038
|
+
directory = _resolve_code_repository_dir(None, path)
|
|
8039
|
+
source_root = _mainsequence_source_checkout_root()
|
|
8040
|
+
try:
|
|
8041
|
+
catalog = fetch_platform_code_repository_skill_catalog()
|
|
8042
|
+
destination, installed = install_platform_code_repository_skills(
|
|
8043
|
+
code_repository_dir=directory,
|
|
8044
|
+
platform_catalog=catalog,
|
|
8045
|
+
protected_code_repository_roots=(source_root,) if source_root else (),
|
|
8046
|
+
)
|
|
8047
|
+
except (
|
|
8048
|
+
ApiError,
|
|
8049
|
+
CodeRepositorySkillAssemblyError,
|
|
8050
|
+
ScaffoldSkillCopyBlocked,
|
|
8051
|
+
FileNotFoundError,
|
|
8052
|
+
OSError,
|
|
8053
|
+
ValueError,
|
|
8054
|
+
) as exc:
|
|
8055
|
+
error(str(exc))
|
|
8056
|
+
raise typer.Exit(1) from exc
|
|
8057
|
+
payload = {
|
|
8058
|
+
"code_repository": directory,
|
|
8059
|
+
"namespace": "mainsequence_platform",
|
|
8060
|
+
"destination_root": destination,
|
|
8061
|
+
"sentinel_path": destination / "PINNED_FROM.txt",
|
|
8062
|
+
"platform": {
|
|
8063
|
+
"source_url": catalog.source_url,
|
|
8064
|
+
"manifest_version": catalog.manifest_version,
|
|
8065
|
+
"manifest_sha256": catalog.manifest_sha256,
|
|
8066
|
+
"ontology_uri": catalog.ontology_uri,
|
|
8067
|
+
"ontology_sha256": catalog.ontology_sha256,
|
|
8068
|
+
},
|
|
8069
|
+
"updated_count": len(installed),
|
|
8070
|
+
"updated": [dataclasses.asdict(item) for item in installed],
|
|
8071
|
+
}
|
|
8072
|
+
if _emit_json(payload):
|
|
8073
|
+
return
|
|
8074
|
+
success("Updated .agents/skills/mainsequence_platform from the authenticated platform catalog.")
|
|
8075
|
+
|
|
8076
|
+
|
|
8064
8077
|
@skills.command("list")
|
|
8065
8078
|
def skills_list_cmd():
|
|
8066
8079
|
"""
|
|
@@ -756,6 +756,24 @@ def secure_store_available() -> bool:
|
|
|
756
756
|
return _secure_keyring_backend() is not None
|
|
757
757
|
|
|
758
758
|
|
|
759
|
+
def saved_session_summary(backend: str | None = None) -> dict:
|
|
760
|
+
"""
|
|
761
|
+
Describe the saved session of a backend without any token value.
|
|
762
|
+
|
|
763
|
+
`usable` says whether its refresh token has not expired by its own claim,
|
|
764
|
+
`expires_at` is that claim in epoch seconds (None when absent) and
|
|
765
|
+
`username` is the recorded user. The process environment is not consulted.
|
|
766
|
+
"""
|
|
767
|
+
tokens = _read_secure_tokens(backend)
|
|
768
|
+
refresh = (tokens.get("refresh") or "").strip()
|
|
769
|
+
expires_at = token_expiry(refresh)
|
|
770
|
+
return {
|
|
771
|
+
"username": str(tokens.get("username") or ""),
|
|
772
|
+
"expires_at": expires_at,
|
|
773
|
+
"usable": bool(refresh) and (expires_at is None or expires_at > int(time.time())),
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
|
|
759
777
|
def stored_session_available(backend: str | None = None) -> bool:
|
|
760
778
|
"""
|
|
761
779
|
Return whether the credential store holds a renewable session for a backend.
|
|
@@ -70,42 +70,145 @@ def set_mainsequence_endpoint(endpoint: str) -> None:
|
|
|
70
70
|
AUTH_ENDPOINT = normalized
|
|
71
71
|
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
_SESSION_TOKEN_VARIABLES = ("MAINSEQUENCE_ACCESS_TOKEN", "MAINSEQUENCE_REFRESH_TOKEN")
|
|
74
|
+
# Renewal answers that refuse the credentials, as opposed to a backend that failed.
|
|
75
|
+
_REFUSED_RENEWAL_STATUSES = frozenset({400, 401, 403})
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _format_utc(epoch: int) -> str:
|
|
79
|
+
return datetime.datetime.fromtimestamp(epoch, tz=datetime.UTC).strftime(
|
|
80
|
+
"%Y-%m-%d %H:%M UTC"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _session_backend() -> str:
|
|
85
|
+
return (AUTH_ENDPOINT or "").rstrip("/")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _login_command(backend: str) -> str:
|
|
74
89
|
"""
|
|
75
|
-
|
|
90
|
+
Return the CLI command that signs in to `backend`.
|
|
76
91
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
pair may belong to another user or backend; the message names it instead.
|
|
92
|
+
`mainsequence login` alone signs in to the configured backend. Another
|
|
93
|
+
backend needs its address and the folder that holds its repositories.
|
|
80
94
|
"""
|
|
81
95
|
try:
|
|
82
|
-
from mainsequence import bootstrap
|
|
83
96
|
from mainsequence.cli import config as cli_config
|
|
84
97
|
|
|
85
|
-
|
|
86
|
-
|
|
98
|
+
configured = cli_config.normalize_backend_url(
|
|
99
|
+
cli_config.get_persistent_config().get("backend_url")
|
|
100
|
+
)
|
|
87
101
|
except Exception:
|
|
88
|
-
|
|
102
|
+
configured = ""
|
|
103
|
+
if not backend or backend == configured:
|
|
104
|
+
return "`mainsequence login`"
|
|
105
|
+
return f"`mainsequence login {backend} <base folder>`"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _env_file_holding_process_credentials() -> pathlib.Path | None:
|
|
109
|
+
"""
|
|
110
|
+
Return the working directory's `.env` when it holds this process's tokens.
|
|
111
|
+
|
|
112
|
+
IDE run configurations, launchers and dotenv calls load that file into a
|
|
113
|
+
process's environment. Values are compared here and never shown.
|
|
114
|
+
"""
|
|
115
|
+
from mainsequence.cli import config as cli_config
|
|
116
|
+
|
|
117
|
+
env_path = pathlib.Path.cwd() / ".env"
|
|
118
|
+
try:
|
|
119
|
+
lines = env_path.read_text(encoding="utf-8").splitlines()
|
|
120
|
+
except (OSError, UnicodeError):
|
|
121
|
+
return None
|
|
122
|
+
for line in lines:
|
|
123
|
+
key = cli_config.env_line_key(line)
|
|
124
|
+
if key not in _SESSION_TOKEN_VARIABLES:
|
|
125
|
+
continue
|
|
126
|
+
value = line.split("=", 1)[1].split(" #", 1)[0].strip().strip("\"'")
|
|
127
|
+
if value and value == (os.environ.get(key) or "").strip():
|
|
128
|
+
return env_path
|
|
129
|
+
return None
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _jwt_reauth_hint(refresh_token: str | None = None) -> str:
|
|
133
|
+
"""
|
|
134
|
+
Say why the backend refused this process's credentials and how to repair them.
|
|
135
|
+
|
|
136
|
+
The facts come from this process and this machine: the refresh token's own
|
|
137
|
+
expiry, whether the credentials were already in the environment when the SDK
|
|
138
|
+
started (and which `.env` holds them, when one does), and the saved session
|
|
139
|
+
of the backend. A pair from the environment wins over the saved session, so
|
|
140
|
+
the saved session is named and not used: the pair may belong to another user
|
|
141
|
+
or backend. No token value is included.
|
|
142
|
+
"""
|
|
143
|
+
backend = _session_backend()
|
|
144
|
+
refresh = refresh_token if refresh_token is not None else os.getenv("MAINSEQUENCE_REFRESH_TOKEN")
|
|
145
|
+
expiry = _decode_jwt_exp(refresh)
|
|
146
|
+
try:
|
|
147
|
+
from mainsequence import bootstrap
|
|
148
|
+
|
|
149
|
+
source = bootstrap.credential_source()
|
|
150
|
+
from_environment = source == bootstrap.CREDENTIALS_FROM_ENVIRONMENT
|
|
151
|
+
from_store = source == bootstrap.CREDENTIALS_FROM_STORE
|
|
152
|
+
except Exception:
|
|
153
|
+
from_environment = from_store = False
|
|
154
|
+
login = _login_command(backend)
|
|
155
|
+
|
|
156
|
+
parts = []
|
|
157
|
+
if expiry is not None and expiry <= int(time.time()):
|
|
158
|
+
parts.append(f" The refresh token expired on {_format_utc(expiry)}.")
|
|
159
|
+
elif expiry is not None:
|
|
160
|
+
parts.append(
|
|
161
|
+
" The refresh token has not expired: the backend revoked it, or it was "
|
|
162
|
+
"issued by another backend."
|
|
163
|
+
)
|
|
89
164
|
|
|
90
165
|
if from_environment:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
166
|
+
try:
|
|
167
|
+
env_file = _env_file_holding_process_credentials()
|
|
168
|
+
except Exception:
|
|
169
|
+
env_file = None
|
|
170
|
+
try:
|
|
171
|
+
from mainsequence.cli import config as cli_config
|
|
172
|
+
|
|
173
|
+
saved = cli_config.saved_session_summary(backend)
|
|
174
|
+
except Exception:
|
|
175
|
+
saved = {"usable": False, "username": "", "expires_at": None}
|
|
176
|
+
if env_file is not None:
|
|
177
|
+
parts.append(
|
|
178
|
+
f" These credentials come from the token lines of {env_file}, not from "
|
|
179
|
+
"your saved session."
|
|
180
|
+
)
|
|
181
|
+
cleanup = f"run `mainsequence refresh-token` in {env_file.parent} to remove those lines"
|
|
182
|
+
else:
|
|
183
|
+
parts.append(
|
|
184
|
+
" These credentials were in this process's environment "
|
|
185
|
+
"(MAINSEQUENCE_ACCESS_TOKEN / MAINSEQUENCE_REFRESH_TOKEN) when it started, "
|
|
186
|
+
"set by the program that started it, not taken from your saved session."
|
|
187
|
+
)
|
|
188
|
+
cleanup = "start the process without those two variables"
|
|
189
|
+
if saved["usable"]:
|
|
190
|
+
who = f" ({saved['username']})" if saved["username"] else ""
|
|
191
|
+
until = (
|
|
192
|
+
f", valid until {_format_utc(saved['expires_at'])}"
|
|
193
|
+
if saved["expires_at"] is not None
|
|
194
|
+
else ""
|
|
195
|
+
)
|
|
196
|
+
parts.append(
|
|
197
|
+
f" Your saved session for {backend}{who} is usable{until}: "
|
|
198
|
+
f"{cleanup}, and the next start uses it."
|
|
102
199
|
)
|
|
200
|
+
else:
|
|
201
|
+
parts.append(
|
|
202
|
+
f" There is no usable saved session for {backend}. Sign in with {login}, "
|
|
203
|
+
f"then {cleanup}."
|
|
204
|
+
)
|
|
205
|
+
elif from_store:
|
|
206
|
+
parts.append(
|
|
207
|
+
f" These credentials are your saved session for {backend}. Sign in again with {login}."
|
|
103
208
|
)
|
|
104
|
-
|
|
105
|
-
"
|
|
106
|
-
|
|
107
|
-
"use `mainsequence login --export` and load the exported env vars there."
|
|
108
|
-
)
|
|
209
|
+
else:
|
|
210
|
+
parts.append(f" Sign in with {login}.")
|
|
211
|
+
return "".join(parts)
|
|
109
212
|
|
|
110
213
|
|
|
111
214
|
def _env_has_value(name: str) -> bool:
|
|
@@ -397,10 +500,14 @@ class JWTAuthProvider(BaseAuthProvider):
|
|
|
397
500
|
if not force and not self._needs_refresh():
|
|
398
501
|
return
|
|
399
502
|
|
|
503
|
+
backend = _session_backend()
|
|
400
504
|
if not self.refresh_token:
|
|
401
505
|
if self.access_token and not force:
|
|
402
506
|
return
|
|
403
|
-
raise AuthError(
|
|
507
|
+
raise AuthError(
|
|
508
|
+
f"Main Sequence cannot renew the session of this process for {backend}: "
|
|
509
|
+
"it has no refresh token." + _jwt_reauth_hint(self.refresh_token)
|
|
510
|
+
)
|
|
404
511
|
|
|
405
512
|
http_client = session or requests
|
|
406
513
|
|
|
@@ -411,16 +518,24 @@ class JWTAuthProvider(BaseAuthProvider):
|
|
|
411
518
|
timeout=self.timeout,
|
|
412
519
|
)
|
|
413
520
|
|
|
521
|
+
if r.status_code in _REFUSED_RENEWAL_STATUSES:
|
|
522
|
+
raise AuthError(
|
|
523
|
+
f"Main Sequence refused to renew the session of this process for {backend} "
|
|
524
|
+
f"(HTTP {r.status_code})." + _jwt_reauth_hint(self.refresh_token)
|
|
525
|
+
)
|
|
414
526
|
if r.status_code != 200:
|
|
527
|
+
# The backend failed; the credentials were not judged.
|
|
415
528
|
raise AuthError(
|
|
416
|
-
f"
|
|
529
|
+
f"Main Sequence could not renew the session of this process for {backend}: "
|
|
530
|
+
f"the backend answered HTTP {r.status_code}. Try again when it is available."
|
|
417
531
|
)
|
|
418
532
|
|
|
419
533
|
data = r.json()
|
|
420
534
|
access = data.get("access")
|
|
421
535
|
if not access:
|
|
422
536
|
raise AuthError(
|
|
423
|
-
"
|
|
537
|
+
f"Main Sequence renewed the session for {backend} without an access token."
|
|
538
|
+
+ _jwt_reauth_hint(self.refresh_token)
|
|
424
539
|
)
|
|
425
540
|
|
|
426
541
|
# Important if ROTATE_REFRESH_TOKENS=True
|
|
@@ -604,15 +719,12 @@ def make_request(
|
|
|
604
719
|
|
|
605
720
|
if r.status_code == 401 and loaders is not None and not auth_retried:
|
|
606
721
|
logger.warning(f"Error {r.status_code}; forcing auth refresh once")
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
logger.exception("Auth refresh failed")
|
|
614
|
-
keep_request = False
|
|
615
|
-
break
|
|
722
|
+
# A refused renewal raises AuthError: it is reported below with the
|
|
723
|
+
# reason and the repair, not as a traceback.
|
|
724
|
+
s.headers.update(loaders.refresh_headers(force=True, session=s))
|
|
725
|
+
req = get_req(session=s)
|
|
726
|
+
auth_retried = True
|
|
727
|
+
continue
|
|
616
728
|
|
|
617
729
|
keep_request = False
|
|
618
730
|
break
|