diff --git a/.github/ci/suites.v1.json b/.github/ci/suites.v1.json index 1dadd748df..60f1f761c8 100644 --- a/.github/ci/suites.v1.json +++ b/.github/ci/suites.v1.json @@ -410,7 +410,12 @@ "tests/test_skill_install_source.py", "tests/test_skills_hub_streaming.py", "tests/test_skills_hub_streaming_faults.py", - "src/opensquilla/application/skill_source.py" + "src/opensquilla/application/skill_source.py", + "tests/test_engine/test_skill_install_settlement.py", + "tests/test_gateway/test_skill_install_status.py", + "tests/test_skills/test_hub_install_operations.py", + "tests/test_skills/test_staging_io_worker.py", + "src/opensquilla/engine/agent.py" ] }, "managed-toolchain": { diff --git a/.github/scripts/plan_ci.py b/.github/scripts/plan_ci.py index 47e24cb394..764e582a05 100644 --- a/.github/scripts/plan_ci.py +++ b/.github/scripts/plan_ci.py @@ -168,6 +168,10 @@ "tests/test_skills_hub_lockfile_contract.py", "tests/test_skills_hub_doctor.py", "tests/test_skills_hash_consumers.py", + "tests/test_engine/test_skill_install_settlement.py", + "tests/test_gateway/test_skill_install_status.py", + "tests/test_skills/test_hub_install_operations.py", + "tests/test_skills/test_staging_io_worker.py", "tests/test_skill_install_source.py", "tests/test_skills_hub_streaming.py", "tests/test_skills_hub_streaming_faults.py", @@ -203,6 +207,7 @@ "src/opensquilla/cli/skills_meta_cmd.py", "src/opensquilla/application/skill_catalog.py", "src/opensquilla/application/skill_management.py", + "src/opensquilla/engine/agent.py", "src/opensquilla/application/skill_source.py", "src/opensquilla/application/skill_proposal_review.py", "src/opensquilla/gateway/app.py", diff --git a/.github/scripts/windows_test_assignments.json b/.github/scripts/windows_test_assignments.json index 73f1844ec9..32bbe5ff17 100644 --- a/.github/scripts/windows_test_assignments.json +++ b/.github/scripts/windows_test_assignments.json @@ -283,6 +283,7 @@ "tests/test_skills/test_paper_source_readiness_gate.py", "tests/test_skills/test_short_drama_review_normalizer.py", "tests/test_skills/test_skill_disable_toggle.py", + "tests/test_skills/test_staging_io_worker.py", "tests/test_skills/test_subtitle_burner.py", "tests/test_skills/test_title_card_image.py", "tests/test_skills/test_toolchain_runtime_integration.py", @@ -721,6 +722,7 @@ "tests/test_gateway/test_shutdown_order.py", "tests/test_gateway/test_skill_catalog_adapter.py", "tests/test_gateway/test_skill_catalog_application.py", + "tests/test_gateway/test_skill_install_status.py", "tests/test_gateway/test_skill_management_adapter.py", "tests/test_gateway/test_skill_management_application.py", "tests/test_gateway/test_skill_management_service_injection.py", @@ -1364,6 +1366,7 @@ "tests/test_engine/test_runtime_error_persistence.py", "tests/test_engine/test_runtime_meta_invoke_surfacing.py", "tests/test_engine/test_runtime_tool_run_budget.py", + "tests/test_engine/test_skill_install_settlement.py", "tests/test_engine/test_spawn_depth_unified.py", "tests/test_engine/test_stream_repetition_guard.py", "tests/test_engine/test_stream_wrappers.py", @@ -1468,6 +1471,7 @@ "tests/test_skills/test_clarify_skip_summary.py", "tests/test_skills/test_creator_trigger_no_collision.py", "tests/test_skills/test_history_explorer.py", + "tests/test_skills/test_hub_install_operations.py", "tests/test_skills/test_meta_auto_trigger_enabled.py", "tests/test_skills/test_meta_on_failure_substitute.py", "tests/test_skills/test_meta_orchestrator_offload.py", diff --git a/.github/scripts/windows_test_durations.json b/.github/scripts/windows_test_durations.json index 6549e76966..6afa33bd89 100644 --- a/.github/scripts/windows_test_durations.json +++ b/.github/scripts/windows_test_durations.json @@ -1591,6 +1591,10 @@ "tests/test_telemetry_server/test_client_pipeline.py": 0.01, "tests/test_scripts/test_gateway_ux.py": 0.01, "tests/test_skills_hub_streaming.py": 0.01, - "tests/test_skills_hub_streaming_faults.py": 0.01 + "tests/test_skills_hub_streaming_faults.py": 0.01, + "tests/test_engine/test_skill_install_settlement.py": 0.01, + "tests/test_gateway/test_skill_install_status.py": 0.01, + "tests/test_skills/test_hub_install_operations.py": 0.01, + "tests/test_skills/test_staging_io_worker.py": 0.01 } } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index abc3837ec5..fa5c96beb6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2057,6 +2057,10 @@ jobs: tests/test_skills_hub_doctor.py \ tests/test_skills_hash_consumers.py \ tests/test_skill_install_source.py \ + tests/test_engine/test_skill_install_settlement.py \ + tests/test_gateway/test_skill_install_status.py \ + tests/test_skills/test_hub_install_operations.py \ + tests/test_skills/test_staging_io_worker.py \ tests/test_skills_hub_streaming.py \ tests/test_skills_hub_streaming_faults.py \ tests/test_skills/test_hub_management_service.py \ diff --git a/contracts/gateway/v4/compatibility-manifest.generated.json b/contracts/gateway/v4/compatibility-manifest.generated.json index d1a933dcae..6198b9609a 100644 --- a/contracts/gateway/v4/compatibility-manifest.generated.json +++ b/contracts/gateway/v4/compatibility-manifest.generated.json @@ -1321,6 +1321,12 @@ "schema": "skills/skills-install-cancel.schema.json", "schemaSha256": "e22bfcec3766dab93bb4db02d25847fceb3b31446c7fe1807b6ea8fdc0cac666" }, + { + "lifecycle": "stable", + "name": "skills.install.status", + "schema": "skills/skills-install-status.schema.json", + "schemaSha256": "8ed97d3207777b114f8efb90bbe9750d0964e694b2fcbcdc95f8cd12c658d57a" + }, { "lifecycle": "stable", "name": "skills.list", @@ -1457,10 +1463,10 @@ "protocol": "opensquilla-websocket-json", "source": { "eventFamilyCount": 10, - "generatorSha256": "0e0b513d7844d926e5f2c6065e64e4b523a1f8df2023317327ee6e05bfa29038", - "methodCount": 214, - "schemaCount": 224, - "schemaTreeSha256": "7479e55aa84897a526ea6a8c8b5dca3f995ffb7fdde4dc57bc84123e21cdf8fc" + "generatorSha256": "f781c6d8e31b336c2d2e342784935b3d327c9947cb35e56de8e44a0a83bffae6", + "methodCount": 215, + "schemaCount": 225, + "schemaTreeSha256": "182cd4e1f6016ec223a6bdcf9e76cd6e734764b581e35a4f6c9efc21aeb173a6" }, "wireVersion": 4 } diff --git a/contracts/gateway/v4/production-targets.json b/contracts/gateway/v4/production-targets.json index 1988355c95..eae75a17a7 100644 --- a/contracts/gateway/v4/production-targets.json +++ b/contracts/gateway/v4/production-targets.json @@ -1254,6 +1254,13 @@ "result" ] }, + { + "kind": "method", + "wireName": "skills.install.status", + "roles": [ + "result" + ] + }, { "kind": "method", "wireName": "skills.list", diff --git a/contracts/gateway/v4/skills/skills-install-status.schema.json b/contracts/gateway/v4/skills/skills-install-status.schema.json new file mode 100644 index 0000000000..9ac4bfacf1 --- /dev/null +++ b/contracts/gateway/v4/skills/skills-install-status.schema.json @@ -0,0 +1,247 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://opensquilla.dev/contracts/gateway/v4/skills/skills-install-status.schema.json", + "title": "OpenSquilla skills.install.status Contract", + "type": "object", + "additionalProperties": false, + "properties": { + "request": { + "$ref": "#/$defs/Request" + }, + "response": { + "$ref": "#/$defs/Response" + } + }, + "x-opensquilla-wire": { + "protocol": "opensquilla-websocket-json", + "version": 4, + "compatibility": "exact-json-tree" + }, + "x-opensquilla-codegen": { + "python": { + "tool": "datamodel-code-generator", + "version": "0.75.1", + "target": "pydantic_v2.BaseModel" + }, + "typescript": { + "tool": "json-schema-to-typescript", + "version": "15.0.4" + }, + "runtimeValidation": { + "tool": "ajv", + "version": "8.17.1", + "mode": "standalone-adapter-only" + } + }, + "x-opensquilla-method": { + "name": "skills.install.status", + "kind": "query", + "scope": "operator.admin", + "guestAllowed": false, + "idempotency": "read-only", + "timeout": { + "policy": "caller" + }, + "capability": { + "kind": "method-availability", + "name": "skills.install.status", + "optional": true + }, + "request": "#/$defs/Request", + "params": "#/$defs/Params", + "response": "#/$defs/Response", + "result": "#/$defs/Result", + "errors": [ + { + "code": "INVALID_REQUEST" + }, + { + "code": "UNAUTHORIZED" + }, + { + "code": "UNSUPPORTED" + }, + { + "code": "INTERNAL_ERROR" + } + ] + }, + "$defs": { + "Params": { + "type": "object", + "additionalProperties": true, + "properties": { + "operationId": { + "type": "string" + }, + "operation_id": { + "type": "string" + } + }, + "anyOf": [ + { + "required": [ + "operationId" + ] + }, + { + "required": [ + "operation_id" + ] + } + ] + }, + "Request": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "id", + "method", + "params" + ], + "properties": { + "type": { + "const": "req" + }, + "id": { + "type": "string" + }, + "method": { + "const": "skills.install.status" + }, + "params": { + "$ref": "#/$defs/Params" + } + } + }, + "Result": { + "type": "object", + "additionalProperties": true, + "required": [ + "operationId", + "state", + "phase", + "terminal", + "scope" + ], + "properties": { + "operationId": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "phase": { + "type": "string" + }, + "state": { + "enum": [ + "unknown", + "running", + "succeeded", + "failed", + "cancelled", + "recovery_required" + ] + }, + "terminal": { + "type": "boolean" + }, + "progress": { + "type": "object", + "additionalProperties": true + }, + "result": { + "type": "object", + "required": [ + "success" + ], + "properties": { + "success": { + "type": "boolean" + } + }, + "additionalProperties": true + } + } + }, + "RpcError": { + "type": "object", + "additionalProperties": true, + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "string" + }, + "message": { + "type": "string" + } + } + }, + "Ok": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "id", + "ok", + "payload" + ], + "properties": { + "type": { + "const": "res" + }, + "id": { + "type": "string" + }, + "ok": { + "const": true + }, + "payload": { + "$ref": "#/$defs/Result" + } + } + }, + "Err": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "id", + "ok", + "error" + ], + "properties": { + "type": { + "const": "res" + }, + "id": { + "type": "string" + }, + "ok": { + "const": false + }, + "payload": { + "type": "null" + }, + "error": { + "$ref": "#/$defs/RpcError" + } + } + }, + "Response": { + "oneOf": [ + { + "$ref": "#/$defs/Ok" + }, + { + "$ref": "#/$defs/Err" + } + ] + } + } +} diff --git a/desktop/electron/scripts/test-profile-consolidation-flow.mjs b/desktop/electron/scripts/test-profile-consolidation-flow.mjs index c481e818f2..238b72abd8 100644 --- a/desktop/electron/scripts/test-profile-consolidation-flow.mjs +++ b/desktop/electron/scripts/test-profile-consolidation-flow.mjs @@ -797,8 +797,8 @@ try { invalidCredentialStableCode: skippedCredentialEvent.stableCode, }, null, 2)) } catch (error) { - const desktopLog = await readFile(join(userData, 'logs', 'desktop.log'), 'utf8').catch(() => '') - const gatewayLog = await readFile(join(userData, 'logs', 'gateway.log'), 'utf8').catch(() => '') + const desktopLog = await readFile(join(activeAppUserData, 'logs', 'desktop.log'), 'utf8').catch(() => '') + const gatewayLog = await readFile(join(activeAppUserData, 'logs', 'gateway.log'), 'utf8').catch(() => '') console.error(JSON.stringify({ phase: 'run-error-before-cleanup', error: String(error?.stack || error), diff --git a/opensquilla-webui/src/adapters/gateway/skillCatalogV4.ts b/opensquilla-webui/src/adapters/gateway/skillCatalogV4.ts index 534e47017b..123b5d50f4 100644 --- a/opensquilla-webui/src/adapters/gateway/skillCatalogV4.ts +++ b/opensquilla-webui/src/adapters/gateway/skillCatalogV4.ts @@ -46,6 +46,10 @@ import { type Result as SkillsInstallCancelResult, } from '@/contracts/generated/v4/skillsInstallCancel' import { validateResult as validateSkillsInstallCancelResult } from '@/contracts/generated/v4/skillsInstallCancelValidators.mjs' +import { SKILLS_INSTALL_STATUS_METHOD, type Result as SkillsInstallStatusResult } from '@/contracts/generated/v4/skillsInstallStatus' +import { validateResult as validateSkillsInstallStatusResult } from '@/contracts/generated/v4/skillsInstallStatusValidators.mjs' +import type { SkillInstallStatus } from '@/modules/skillCatalog' + import { SKILLS_DEPS_INSTALL_METHOD, type Params as SkillsDepsInstallParams, @@ -348,6 +352,16 @@ export function createV4SkillCatalog(rpc: RpcTransport): SkillCatalog { if (!validateSkillsInstallResult(result)) throw invalid(SKILLS_INSTALL_METHOD) return result as unknown as SkillInstallResult }, + supportsInstallStatus() { + return rpc.supports(SKILLS_INSTALL_STATUS_METHOD) + }, + async installStatus(operationId, options) { + const result = await rpc.request( + SKILLS_INSTALL_STATUS_METHOD, { operationId }, callOptions(options?.signal), + ) + if (!validateSkillsInstallStatusResult(result)) throw invalid(SKILLS_INSTALL_STATUS_METHOD) + return result as unknown as SkillInstallStatus + }, supportsInstallCancellation() { return rpc.supports(SKILLS_INSTALL_CANCEL_METHOD) }, diff --git a/opensquilla-webui/src/components/skills/SkillsAddDrawer.vue b/opensquilla-webui/src/components/skills/SkillsAddDrawer.vue index f4c7b406e9..971aac6bb0 100644 --- a/opensquilla-webui/src/components/skills/SkillsAddDrawer.vue +++ b/opensquilla-webui/src/components/skills/SkillsAddDrawer.vue @@ -144,7 +144,7 @@ :data-status="item.status" >