@itphx/synth-core 0.2.0 → 0.4.4
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.
- package/definitions/protected-paths.json +36 -0
- package/definitions/shell-policy.json +36 -0
- package/dist/auth/local.d.ts +14 -0
- package/dist/auth/local.d.ts.map +1 -1
- package/dist/auth/local.js +63 -11
- package/dist/auth/local.js.map +1 -1
- package/dist/auth/local.test.js +59 -0
- package/dist/auth/local.test.js.map +1 -1
- package/dist/definitions/protected-paths.json +36 -0
- package/dist/definitions/shell-policy.json +36 -0
- package/dist/engine/agents/definitions/admin.json +3 -1
- package/dist/engine/agents/definitions/analyst.json +3 -1
- package/dist/engine/agents/definitions/assistant.json +2 -2
- package/dist/engine/agents/definitions/auditor.json +4 -1
- package/dist/engine/agents/definitions/developer.json +4 -1
- package/dist/engine/agents/definitions/devops.json +4 -1
- package/dist/engine/agents/definitions/explore.json +3 -3
- package/dist/engine/agents/definitions/general.json +2 -2
- package/dist/engine/agents/definitions/pm.json +5 -1
- package/dist/engine/agents/definitions/qa.json +4 -1
- package/dist/engine/agents/definitions/title.json +2 -2
- package/dist/engine/agents/index.d.ts +4 -0
- package/dist/engine/agents/index.d.ts.map +1 -1
- package/dist/engine/agents/index.js +9 -3
- package/dist/engine/agents/index.js.map +1 -1
- package/dist/engine/agents/mode.d.ts +5 -3
- package/dist/engine/agents/mode.d.ts.map +1 -1
- package/dist/engine/agents/mode.js +6 -6
- package/dist/engine/agents/mode.js.map +1 -1
- package/dist/engine/agents/mode.test.js +9 -3
- package/dist/engine/agents/mode.test.js.map +1 -1
- package/dist/engine/agents/normalize.d.ts +9 -0
- package/dist/engine/agents/normalize.d.ts.map +1 -0
- package/dist/engine/agents/normalize.js +22 -0
- package/dist/engine/agents/normalize.js.map +1 -0
- package/dist/engine/agents/normalize.test.d.ts +2 -0
- package/dist/engine/agents/normalize.test.d.ts.map +1 -0
- package/dist/engine/agents/normalize.test.js +37 -0
- package/dist/engine/agents/normalize.test.js.map +1 -0
- package/dist/engine/agents/registry.d.ts +2 -1
- package/dist/engine/agents/registry.d.ts.map +1 -1
- package/dist/engine/agents/registry.js +7 -6
- package/dist/engine/agents/registry.js.map +1 -1
- package/dist/engine/agents/registry.test.js +24 -0
- package/dist/engine/agents/registry.test.js.map +1 -1
- package/dist/engine/context/config.d.ts +43 -6
- package/dist/engine/context/config.d.ts.map +1 -1
- package/dist/engine/context/config.js +123 -26
- package/dist/engine/context/config.js.map +1 -1
- package/dist/engine/context/config.security.test.d.ts +2 -0
- package/dist/engine/context/config.security.test.d.ts.map +1 -0
- package/dist/engine/context/config.security.test.js +82 -0
- package/dist/engine/context/config.security.test.js.map +1 -0
- package/dist/engine/context/config.test.js +199 -10
- package/dist/engine/context/config.test.js.map +1 -1
- package/dist/engine/context/optimizer.d.ts +20 -1
- package/dist/engine/context/optimizer.d.ts.map +1 -1
- package/dist/engine/context/optimizer.js +63 -29
- package/dist/engine/context/optimizer.js.map +1 -1
- package/dist/engine/context/optimizer.test.js +156 -0
- package/dist/engine/context/optimizer.test.js.map +1 -1
- package/dist/engine/gateway/gateway-provider.d.ts +4 -0
- package/dist/engine/gateway/gateway-provider.d.ts.map +1 -1
- package/dist/engine/gateway/gateway-provider.js +7 -2
- package/dist/engine/gateway/gateway-provider.js.map +1 -1
- package/dist/engine/gateway/gateway-provider.test.js +3 -3
- package/dist/engine/gateway/gateway-provider.test.js.map +1 -1
- package/dist/engine/gateway/input-guard.d.ts.map +1 -1
- package/dist/engine/gateway/input-guard.js +2 -1
- package/dist/engine/gateway/input-guard.js.map +1 -1
- package/dist/engine/gateway/secret-detector.d.ts.map +1 -1
- package/dist/engine/gateway/secret-detector.js +2 -3
- package/dist/engine/gateway/secret-detector.js.map +1 -1
- package/dist/engine/gateway/secret-detector.test.js +4 -4
- package/dist/engine/gateway/secret-detector.test.js.map +1 -1
- package/dist/engine/llm-error.d.ts +23 -0
- package/dist/engine/llm-error.d.ts.map +1 -0
- package/dist/engine/llm-error.js +86 -0
- package/dist/engine/llm-error.js.map +1 -0
- package/dist/engine/llm-error.test.d.ts +2 -0
- package/dist/engine/llm-error.test.d.ts.map +1 -0
- package/dist/engine/llm-error.test.js +101 -0
- package/dist/engine/llm-error.test.js.map +1 -0
- package/dist/engine/orchestrator-deny-abort.test.d.ts +2 -0
- package/dist/engine/orchestrator-deny-abort.test.d.ts.map +1 -0
- package/dist/engine/orchestrator-deny-abort.test.js +414 -0
- package/dist/engine/orchestrator-deny-abort.test.js.map +1 -0
- package/dist/engine/orchestrator-error.test.d.ts +2 -0
- package/dist/engine/orchestrator-error.test.d.ts.map +1 -0
- package/dist/engine/orchestrator-error.test.js +136 -0
- package/dist/engine/orchestrator-error.test.js.map +1 -0
- package/dist/engine/orchestrator.d.ts +123 -10
- package/dist/engine/orchestrator.d.ts.map +1 -1
- package/dist/engine/orchestrator.js +791 -181
- package/dist/engine/orchestrator.js.map +1 -1
- package/dist/engine/orchestrator.test.js +723 -0
- package/dist/engine/orchestrator.test.js.map +1 -1
- package/dist/engine/permission/approval.d.ts.map +1 -1
- package/dist/engine/permission/approval.js +29 -10
- package/dist/engine/permission/approval.js.map +1 -1
- package/dist/engine/permission/approval.test.js +57 -0
- package/dist/engine/permission/approval.test.js.map +1 -1
- package/dist/engine/permission/evaluator.d.ts +13 -0
- package/dist/engine/permission/evaluator.d.ts.map +1 -1
- package/dist/engine/permission/evaluator.js +121 -22
- package/dist/engine/permission/evaluator.js.map +1 -1
- package/dist/engine/permission/evaluator.test.js +188 -4
- package/dist/engine/permission/evaluator.test.js.map +1 -1
- package/dist/engine/permission/groups.d.ts.map +1 -1
- package/dist/engine/permission/groups.js +19 -1
- package/dist/engine/permission/groups.js.map +1 -1
- package/dist/engine/permission/integration.test.js +165 -4
- package/dist/engine/permission/integration.test.js.map +1 -1
- package/dist/engine/plan.integration.test.js +142 -33
- package/dist/engine/plan.integration.test.js.map +1 -1
- package/dist/engine/router/micro/tasks/classify-intent.d.ts.map +1 -1
- package/dist/engine/router/micro/tasks/classify-intent.js +24 -18
- package/dist/engine/router/micro/tasks/classify-intent.js.map +1 -1
- package/dist/engine/router/micro/tasks/classify-intent.test.js +4 -4
- package/dist/engine/router/micro/tasks/classify-intent.test.js.map +1 -1
- package/dist/engine/router/micro/tasks/enum-decision.d.ts.map +1 -1
- package/dist/engine/router/micro/tasks/enum-decision.js +14 -8
- package/dist/engine/router/micro/tasks/enum-decision.js.map +1 -1
- package/dist/engine/router/micro/tasks/enum-decision.test.js +2 -2
- package/dist/engine/router/micro/tasks/enum-decision.test.js.map +1 -1
- package/dist/engine/router/micro/tasks/small-talk.d.ts.map +1 -1
- package/dist/engine/router/micro/tasks/small-talk.js +20 -17
- package/dist/engine/router/micro/tasks/small-talk.js.map +1 -1
- package/dist/engine/router/micro/types.d.ts +2 -0
- package/dist/engine/router/micro/types.d.ts.map +1 -1
- package/dist/engine/router/provider-pool.d.ts +2 -2
- package/dist/engine/router/provider-pool.d.ts.map +1 -1
- package/dist/engine/router/provider-pool.js +5 -3
- package/dist/engine/router/provider-pool.js.map +1 -1
- package/dist/engine/router/quality-checker.d.ts.map +1 -1
- package/dist/engine/router/quality-checker.js +15 -14
- package/dist/engine/router/quality-checker.js.map +1 -1
- package/dist/engine/router/router.d.ts +2 -2
- package/dist/engine/router/router.d.ts.map +1 -1
- package/dist/engine/router/router.js +8 -5
- package/dist/engine/router/router.js.map +1 -1
- package/dist/engine/router/utils.d.ts +2 -2
- package/dist/engine/router/utils.d.ts.map +1 -1
- package/dist/engine/router/utils.js +2 -1
- package/dist/engine/router/utils.js.map +1 -1
- package/dist/engine/security/index.d.ts +6 -0
- package/dist/engine/security/index.d.ts.map +1 -1
- package/dist/engine/security/index.js +3 -0
- package/dist/engine/security/index.js.map +1 -1
- package/dist/engine/security/integration.test.js +11 -11
- package/dist/engine/security/integration.test.js.map +1 -1
- package/dist/engine/security/prompt-injection.d.ts +1 -1
- package/dist/engine/security/prompt-injection.d.ts.map +1 -1
- package/dist/engine/security/prompt-injection.js +2 -1
- package/dist/engine/security/prompt-injection.js.map +1 -1
- package/dist/engine/security/prompt-injection.test.js +1 -1
- package/dist/engine/security/prompt-injection.test.js.map +1 -1
- package/dist/engine/security/protected-paths-store.d.ts +56 -0
- package/dist/engine/security/protected-paths-store.d.ts.map +1 -0
- package/dist/engine/security/protected-paths-store.js +239 -0
- package/dist/engine/security/protected-paths-store.js.map +1 -0
- package/dist/engine/security/protected-paths.d.ts +54 -0
- package/dist/engine/security/protected-paths.d.ts.map +1 -0
- package/dist/engine/security/protected-paths.js +193 -0
- package/dist/engine/security/protected-paths.js.map +1 -0
- package/dist/engine/security/protected-paths.test.d.ts +2 -0
- package/dist/engine/security/protected-paths.test.d.ts.map +1 -0
- package/dist/engine/security/protected-paths.test.js +166 -0
- package/dist/engine/security/protected-paths.test.js.map +1 -0
- package/dist/engine/security/shell-guard.d.ts +117 -0
- package/dist/engine/security/shell-guard.d.ts.map +1 -0
- package/dist/engine/security/shell-guard.js +578 -0
- package/dist/engine/security/shell-guard.js.map +1 -0
- package/dist/engine/security/shell-guard.test.d.ts +2 -0
- package/dist/engine/security/shell-guard.test.d.ts.map +1 -0
- package/dist/engine/security/shell-guard.test.js +378 -0
- package/dist/engine/security/shell-guard.test.js.map +1 -0
- package/dist/engine/system-messages.d.ts +237 -0
- package/dist/engine/system-messages.d.ts.map +1 -0
- package/dist/engine/system-messages.js +276 -0
- package/dist/engine/system-messages.js.map +1 -0
- package/dist/engine/system-messages.test.d.ts +2 -0
- package/dist/engine/system-messages.test.d.ts.map +1 -0
- package/dist/engine/system-messages.test.js +81 -0
- package/dist/engine/system-messages.test.js.map +1 -0
- package/dist/engine/tools/abort.d.ts +21 -0
- package/dist/engine/tools/abort.d.ts.map +1 -0
- package/dist/engine/tools/abort.js +44 -0
- package/dist/engine/tools/abort.js.map +1 -0
- package/dist/engine/tools/classify-conversation.d.ts.map +1 -1
- package/dist/engine/tools/classify-conversation.js +7 -2
- package/dist/engine/tools/classify-conversation.js.map +1 -1
- package/dist/engine/tools/fs-read.d.ts.map +1 -1
- package/dist/engine/tools/fs-read.js +17 -6
- package/dist/engine/tools/fs-read.js.map +1 -1
- package/dist/engine/tools/fs-read.test.js +43 -0
- package/dist/engine/tools/fs-read.test.js.map +1 -1
- package/dist/engine/tools/fs-write.d.ts.map +1 -1
- package/dist/engine/tools/fs-write.js +47 -17
- package/dist/engine/tools/fs-write.js.map +1 -1
- package/dist/engine/tools/git.d.ts +2 -1
- package/dist/engine/tools/git.d.ts.map +1 -1
- package/dist/engine/tools/git.js +85 -17
- package/dist/engine/tools/git.js.map +1 -1
- package/dist/engine/tools/git.test.js +39 -0
- package/dist/engine/tools/git.test.js.map +1 -1
- package/dist/engine/tools/index.d.ts.map +1 -1
- package/dist/engine/tools/index.js +16 -2
- package/dist/engine/tools/index.js.map +1 -1
- package/dist/engine/tools/json-schema.js +38 -0
- package/dist/engine/tools/json-schema.js.map +1 -1
- package/dist/engine/tools/json-schema.test.d.ts +2 -0
- package/dist/engine/tools/json-schema.test.d.ts.map +1 -0
- package/dist/engine/tools/json-schema.test.js +40 -0
- package/dist/engine/tools/json-schema.test.js.map +1 -0
- package/dist/engine/tools/path-utils.d.ts.map +1 -1
- package/dist/engine/tools/path-utils.js +24 -0
- package/dist/engine/tools/path-utils.js.map +1 -1
- package/dist/engine/tools/path-utils.test.js +15 -0
- package/dist/engine/tools/path-utils.test.js.map +1 -1
- package/dist/engine/tools/plan.d.ts +11 -3
- package/dist/engine/tools/plan.d.ts.map +1 -1
- package/dist/engine/tools/plan.js +326 -95
- package/dist/engine/tools/plan.js.map +1 -1
- package/dist/engine/tools/plan.test.js +161 -47
- package/dist/engine/tools/plan.test.js.map +1 -1
- package/dist/engine/tools/project-context.d.ts.map +1 -1
- package/dist/engine/tools/project-context.js +54 -42
- package/dist/engine/tools/project-context.js.map +1 -1
- package/dist/engine/tools/project-context.test.js +37 -22
- package/dist/engine/tools/project-context.test.js.map +1 -1
- package/dist/engine/tools/project-manage.d.ts +24 -0
- package/dist/engine/tools/project-manage.d.ts.map +1 -0
- package/dist/engine/tools/project-manage.js +311 -0
- package/dist/engine/tools/project-manage.js.map +1 -0
- package/dist/engine/tools/project-manage.test.d.ts +2 -0
- package/dist/engine/tools/project-manage.test.d.ts.map +1 -0
- package/dist/engine/tools/project-manage.test.js +196 -0
- package/dist/engine/tools/project-manage.test.js.map +1 -0
- package/dist/engine/tools/read-reference.d.ts.map +1 -1
- package/dist/engine/tools/read-reference.js +10 -5
- package/dist/engine/tools/read-reference.js.map +1 -1
- package/dist/engine/tools/read-reference.test.js +5 -2
- package/dist/engine/tools/read-reference.test.js.map +1 -1
- package/dist/engine/tools/search.d.ts.map +1 -1
- package/dist/engine/tools/search.js +16 -6
- package/dist/engine/tools/search.js.map +1 -1
- package/dist/engine/tools/shell.d.ts.map +1 -1
- package/dist/engine/tools/shell.js +17 -4
- package/dist/engine/tools/shell.js.map +1 -1
- package/dist/engine/tools/shell.test.js +63 -1
- package/dist/engine/tools/shell.test.js.map +1 -1
- package/dist/engine/tools/task-resume.d.ts +93 -0
- package/dist/engine/tools/task-resume.d.ts.map +1 -0
- package/dist/engine/tools/task-resume.js +269 -0
- package/dist/engine/tools/task-resume.js.map +1 -0
- package/dist/engine/tools/task-resume.test.d.ts +2 -0
- package/dist/engine/tools/task-resume.test.d.ts.map +1 -0
- package/dist/engine/tools/task-resume.test.js +423 -0
- package/dist/engine/tools/task-resume.test.js.map +1 -0
- package/dist/engine/tools/task.d.ts +81 -2
- package/dist/engine/tools/task.d.ts.map +1 -1
- package/dist/engine/tools/task.js +898 -471
- package/dist/engine/tools/task.js.map +1 -1
- package/dist/engine/tools/task.test.js +371 -3
- package/dist/engine/tools/task.test.js.map +1 -1
- package/dist/engine/tools/todo.d.ts +27 -1
- package/dist/engine/tools/todo.d.ts.map +1 -1
- package/dist/engine/tools/todo.js +320 -14
- package/dist/engine/tools/todo.js.map +1 -1
- package/dist/engine/tools/todo.test.js +150 -48
- package/dist/engine/tools/todo.test.js.map +1 -1
- package/dist/engine/tools/web.d.ts.map +1 -1
- package/dist/engine/tools/web.js +65 -18
- package/dist/engine/tools/web.js.map +1 -1
- package/dist/engine/tools/web.test.js +94 -2
- package/dist/engine/tools/web.test.js.map +1 -1
- package/dist/events/bus.d.ts +7 -0
- package/dist/events/bus.d.ts.map +1 -1
- package/dist/events/bus.js +26 -3
- package/dist/events/bus.js.map +1 -1
- package/dist/events/bus.test.js +46 -0
- package/dist/events/bus.test.js.map +1 -1
- package/dist/index.d.ts +11 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +0 -1
- package/dist/logger.js.map +1 -1
- package/dist/main.js +250 -10
- package/dist/main.js.map +1 -1
- package/dist/prompts/roles/ADMIN.en.md +12 -11
- package/dist/prompts/roles/ADMIN.ru.md +12 -11
- package/dist/prompts/roles/ANALYST.en.md +12 -11
- package/dist/prompts/roles/ANALYST.ru.md +12 -11
- package/dist/prompts/roles/ASSISTANT.en.md +12 -11
- package/dist/prompts/roles/ASSISTANT.ru.md +12 -11
- package/dist/prompts/roles/AUDITOR.en.md +12 -11
- package/dist/prompts/roles/AUDITOR.ru.md +12 -11
- package/dist/prompts/roles/DEVELOPER.en.md +12 -11
- package/dist/prompts/roles/DEVELOPER.ru.md +12 -11
- package/dist/prompts/roles/DEVOPS.en.md +12 -11
- package/dist/prompts/roles/DEVOPS.ru.md +12 -11
- package/dist/prompts/roles/PM.en.md +12 -11
- package/dist/prompts/roles/PM.ru.md +12 -11
- package/dist/prompts/roles/QA.en.md +12 -11
- package/dist/prompts/roles/QA.ru.md +12 -11
- package/dist/prompts/roles/TITLE.en.md +30 -0
- package/dist/prompts/roles/TITLE.ru.md +3 -11
- package/dist/prompts/security/ask-protected-path.en.md +1 -0
- package/dist/prompts/security/ask-protected-path.ru.md +1 -0
- package/dist/prompts/security/deny-command.en.md +1 -0
- package/dist/prompts/security/deny-command.ru.md +1 -0
- package/dist/prompts/security/deny-protected-dir.en.md +1 -0
- package/dist/prompts/security/deny-protected-dir.ru.md +1 -0
- package/dist/prompts/security/deny-protected-path.en.md +1 -0
- package/dist/prompts/security/deny-protected-path.ru.md +1 -0
- package/dist/prompts/subagents/explore.en.md +23 -0
- package/dist/prompts/subagents/general.en.md +19 -0
- package/dist/prompts/system/MODE_ACT.en.md +20 -0
- package/{prompts/modes/act.md → dist/prompts/system/MODE_ACT.ru.md} +2 -0
- package/dist/prompts/system/MODE_PLAN.en.md +22 -0
- package/dist/prompts/{modes/plan.md → system/MODE_PLAN.ru.md} +4 -3
- package/dist/prompts/system/MODE_YOLO.en.md +22 -0
- package/{prompts/modes/yolo.md → dist/prompts/system/MODE_YOLO.ru.md} +2 -0
- package/dist/prompts/system/PLAN_GUIDE.en.md +36 -0
- package/dist/prompts/system/PLAN_GUIDE.ru.md +33 -0
- package/dist/prompts/system/PLAN_MODE_AUTO.en.md +11 -0
- package/dist/prompts/system/PLAN_MODE_AUTO.ru.md +12 -0
- package/dist/prompts/system/PLAN_MODE_STRICT_APPROVED.en.md +8 -0
- package/dist/prompts/system/PLAN_MODE_STRICT_APPROVED.ru.md +8 -0
- package/dist/prompts/system/PLAN_MODE_STRICT_NOT_APPROVED.en.md +7 -0
- package/dist/prompts/system/PLAN_MODE_STRICT_NOT_APPROVED.ru.md +7 -0
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +28 -2
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/base.d.ts +20 -1
- package/dist/providers/base.d.ts.map +1 -1
- package/dist/providers/base.js +17 -0
- package/dist/providers/base.js.map +1 -1
- package/dist/providers/exceptions.d.ts.map +1 -1
- package/dist/providers/exceptions.js +3 -2
- package/dist/providers/exceptions.js.map +1 -1
- package/dist/providers/index.d.ts +1 -0
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +4 -0
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/ollama.d.ts +23 -0
- package/dist/providers/ollama.d.ts.map +1 -1
- package/dist/providers/ollama.js +128 -12
- package/dist/providers/ollama.js.map +1 -1
- package/dist/providers/ollama.test.d.ts +2 -0
- package/dist/providers/ollama.test.d.ts.map +1 -0
- package/dist/providers/ollama.test.js +129 -0
- package/dist/providers/ollama.test.js.map +1 -0
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +82 -9
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/openai.test.d.ts +2 -0
- package/dist/providers/openai.test.d.ts.map +1 -0
- package/dist/providers/openai.test.js +84 -0
- package/dist/providers/openai.test.js.map +1 -0
- package/dist/providers/remote.d.ts +60 -0
- package/dist/providers/remote.d.ts.map +1 -0
- package/dist/providers/remote.js +394 -0
- package/dist/providers/remote.js.map +1 -0
- package/dist/providers/remote.test.d.ts +2 -0
- package/dist/providers/remote.test.d.ts.map +1 -0
- package/dist/providers/remote.test.js +258 -0
- package/dist/providers/remote.test.js.map +1 -0
- package/dist/providers/stream-error.test.d.ts +2 -0
- package/dist/providers/stream-error.test.d.ts.map +1 -0
- package/dist/providers/stream-error.test.js +63 -0
- package/dist/providers/stream-error.test.js.map +1 -0
- package/dist/providers/timeout.test.d.ts +2 -0
- package/dist/providers/timeout.test.d.ts.map +1 -0
- package/dist/providers/timeout.test.js +129 -0
- package/dist/providers/timeout.test.js.map +1 -0
- package/dist/queue/processor.d.ts +22 -0
- package/dist/queue/processor.d.ts.map +1 -1
- package/dist/queue/processor.js +150 -25
- package/dist/queue/processor.js.map +1 -1
- package/dist/queue/processor.test.d.ts +2 -0
- package/dist/queue/processor.test.d.ts.map +1 -0
- package/dist/queue/processor.test.js +211 -0
- package/dist/queue/processor.test.js.map +1 -0
- package/dist/remote/aggregate.d.ts +43 -0
- package/dist/remote/aggregate.d.ts.map +1 -0
- package/dist/remote/aggregate.js +84 -0
- package/dist/remote/aggregate.js.map +1 -0
- package/dist/remote/aggregate.test.d.ts +2 -0
- package/dist/remote/aggregate.test.d.ts.map +1 -0
- package/dist/remote/aggregate.test.js +120 -0
- package/dist/remote/aggregate.test.js.map +1 -0
- package/dist/remote/client.d.ts +142 -0
- package/dist/remote/client.d.ts.map +1 -0
- package/dist/remote/client.js +290 -0
- package/dist/remote/client.js.map +1 -0
- package/dist/remote/client.test.d.ts +2 -0
- package/dist/remote/client.test.d.ts.map +1 -0
- package/dist/remote/client.test.js +176 -0
- package/dist/remote/client.test.js.map +1 -0
- package/dist/remote/connection.d.ts +66 -0
- package/dist/remote/connection.d.ts.map +1 -0
- package/dist/remote/connection.js +298 -0
- package/dist/remote/connection.js.map +1 -0
- package/dist/remote/connection.test.d.ts +2 -0
- package/dist/remote/connection.test.d.ts.map +1 -0
- package/dist/remote/connection.test.js +323 -0
- package/dist/remote/connection.test.js.map +1 -0
- package/dist/remote/credentials.d.ts +13 -0
- package/dist/remote/credentials.d.ts.map +1 -0
- package/dist/remote/credentials.js +81 -0
- package/dist/remote/credentials.js.map +1 -0
- package/dist/remote/credentials.test.d.ts +2 -0
- package/dist/remote/credentials.test.d.ts.map +1 -0
- package/dist/remote/credentials.test.js +69 -0
- package/dist/remote/credentials.test.js.map +1 -0
- package/dist/remote/device-flow.d.ts +72 -0
- package/dist/remote/device-flow.d.ts.map +1 -0
- package/dist/remote/device-flow.js +229 -0
- package/dist/remote/device-flow.js.map +1 -0
- package/dist/remote/device-flow.test.d.ts +2 -0
- package/dist/remote/device-flow.test.d.ts.map +1 -0
- package/dist/remote/device-flow.test.js +103 -0
- package/dist/remote/device-flow.test.js.map +1 -0
- package/dist/remote/event-bridge.d.ts +46 -0
- package/dist/remote/event-bridge.d.ts.map +1 -0
- package/dist/remote/event-bridge.js +218 -0
- package/dist/remote/event-bridge.js.map +1 -0
- package/dist/remote/event-bridge.test.d.ts +2 -0
- package/dist/remote/event-bridge.test.d.ts.map +1 -0
- package/dist/remote/event-bridge.test.js +240 -0
- package/dist/remote/event-bridge.test.js.map +1 -0
- package/dist/remote/index.d.ts +19 -0
- package/dist/remote/index.d.ts.map +1 -0
- package/dist/remote/index.js +11 -0
- package/dist/remote/index.js.map +1 -0
- package/dist/remote/session.d.ts +72 -0
- package/dist/remote/session.d.ts.map +1 -0
- package/dist/remote/session.js +248 -0
- package/dist/remote/session.js.map +1 -0
- package/dist/remote/session.test.d.ts +2 -0
- package/dist/remote/session.test.d.ts.map +1 -0
- package/dist/remote/session.test.js +482 -0
- package/dist/remote/session.test.js.map +1 -0
- package/dist/remote/transfer.d.ts +75 -0
- package/dist/remote/transfer.d.ts.map +1 -0
- package/dist/remote/transfer.js +491 -0
- package/dist/remote/transfer.js.map +1 -0
- package/dist/remote/transfer.test.d.ts +2 -0
- package/dist/remote/transfer.test.d.ts.map +1 -0
- package/dist/remote/transfer.test.js +416 -0
- package/dist/remote/transfer.test.js.map +1 -0
- package/dist/remote/types.d.ts +28 -0
- package/dist/remote/types.d.ts.map +1 -0
- package/dist/remote/types.js +10 -0
- package/dist/remote/types.js.map +1 -0
- package/dist/server/app.d.ts +62 -1
- package/dist/server/app.d.ts.map +1 -1
- package/dist/server/app.js +393 -27
- package/dist/server/app.js.map +1 -1
- package/dist/server/app.proxy.test.d.ts +2 -0
- package/dist/server/app.proxy.test.d.ts.map +1 -0
- package/dist/server/app.proxy.test.js +193 -0
- package/dist/server/app.proxy.test.js.map +1 -0
- package/dist/server/app.remote.test.d.ts +2 -0
- package/dist/server/app.remote.test.d.ts.map +1 -0
- package/dist/server/app.remote.test.js +674 -0
- package/dist/server/app.remote.test.js.map +1 -0
- package/dist/server/chat.d.ts.map +1 -1
- package/dist/server/chat.js +18 -2
- package/dist/server/chat.js.map +1 -1
- package/dist/server/proxy.d.ts +14 -0
- package/dist/server/proxy.d.ts.map +1 -0
- package/dist/server/proxy.js +93 -0
- package/dist/server/proxy.js.map +1 -0
- package/dist/server/routes/agents.d.ts.map +1 -1
- package/dist/server/routes/agents.js +39 -23
- package/dist/server/routes/agents.js.map +1 -1
- package/dist/server/routes/auth.d.ts +26 -2
- package/dist/server/routes/auth.d.ts.map +1 -1
- package/dist/server/routes/auth.js +269 -36
- package/dist/server/routes/auth.js.map +1 -1
- package/dist/server/routes/auth.test.d.ts +2 -0
- package/dist/server/routes/auth.test.d.ts.map +1 -0
- package/dist/server/routes/auth.test.js +466 -0
- package/dist/server/routes/auth.test.js.map +1 -0
- package/dist/server/routes/fs.d.ts +2 -1
- package/dist/server/routes/fs.d.ts.map +1 -1
- package/dist/server/routes/fs.js +340 -7
- package/dist/server/routes/fs.js.map +1 -1
- package/dist/server/routes/hybrid.d.ts +32 -0
- package/dist/server/routes/hybrid.d.ts.map +1 -0
- package/dist/server/routes/hybrid.js +21 -0
- package/dist/server/routes/hybrid.js.map +1 -0
- package/dist/server/routes/hybrid.test.d.ts +2 -0
- package/dist/server/routes/hybrid.test.d.ts.map +1 -0
- package/dist/server/routes/hybrid.test.js +454 -0
- package/dist/server/routes/hybrid.test.js.map +1 -0
- package/dist/server/routes/messages.d.ts +2 -1
- package/dist/server/routes/messages.d.ts.map +1 -1
- package/dist/server/routes/messages.js +104 -1
- package/dist/server/routes/messages.js.map +1 -1
- package/dist/server/routes/models.cache.test.d.ts +2 -0
- package/dist/server/routes/models.cache.test.d.ts.map +1 -0
- package/dist/server/routes/models.cache.test.js +59 -0
- package/dist/server/routes/models.cache.test.js.map +1 -0
- package/dist/server/routes/models.d.ts +9 -1
- package/dist/server/routes/models.d.ts.map +1 -1
- package/dist/server/routes/models.js +38 -3
- package/dist/server/routes/models.js.map +1 -1
- package/dist/server/routes/models.remote.test.d.ts +2 -0
- package/dist/server/routes/models.remote.test.d.ts.map +1 -0
- package/dist/server/routes/models.remote.test.js +101 -0
- package/dist/server/routes/models.remote.test.js.map +1 -0
- package/dist/server/routes/project-transfer.d.ts +16 -0
- package/dist/server/routes/project-transfer.d.ts.map +1 -0
- package/dist/server/routes/project-transfer.js +141 -0
- package/dist/server/routes/project-transfer.js.map +1 -0
- package/dist/server/routes/project-transfer.test.d.ts +2 -0
- package/dist/server/routes/project-transfer.test.d.ts.map +1 -0
- package/dist/server/routes/project-transfer.test.js +199 -0
- package/dist/server/routes/project-transfer.test.js.map +1 -0
- package/dist/server/routes/projects.d.ts +2 -1
- package/dist/server/routes/projects.d.ts.map +1 -1
- package/dist/server/routes/projects.js +101 -5
- package/dist/server/routes/projects.js.map +1 -1
- package/dist/server/routes/providers.d.ts +1 -1
- package/dist/server/routes/providers.d.ts.map +1 -1
- package/dist/server/routes/providers.js +3 -1
- package/dist/server/routes/providers.js.map +1 -1
- package/dist/server/routes/providers.test.d.ts +2 -0
- package/dist/server/routes/providers.test.d.ts.map +1 -0
- package/dist/server/routes/providers.test.js +58 -0
- package/dist/server/routes/providers.test.js.map +1 -0
- package/dist/server/routes/queue.d.ts +2 -1
- package/dist/server/routes/queue.d.ts.map +1 -1
- package/dist/server/routes/queue.js +180 -18
- package/dist/server/routes/queue.js.map +1 -1
- package/dist/server/routes/sessions.d.ts +9 -1
- package/dist/server/routes/sessions.d.ts.map +1 -1
- package/dist/server/routes/sessions.js +269 -18
- package/dist/server/routes/sessions.js.map +1 -1
- package/dist/server/routes/sessions.test.js +102 -2
- package/dist/server/routes/sessions.test.js.map +1 -1
- package/dist/server/routes/settings.d.ts +6 -1
- package/dist/server/routes/settings.d.ts.map +1 -1
- package/dist/server/routes/settings.js +138 -0
- package/dist/server/routes/settings.js.map +1 -1
- package/dist/server/routes/settings.test.js +65 -1
- package/dist/server/routes/settings.test.js.map +1 -1
- package/dist/server/serialize.d.ts +11 -3
- package/dist/server/serialize.d.ts.map +1 -1
- package/dist/server/serialize.js +11 -3
- package/dist/server/serialize.js.map +1 -1
- package/dist/sessions/auto-title.d.ts +1 -1
- package/dist/sessions/auto-title.d.ts.map +1 -1
- package/dist/sessions/auto-title.js +24 -13
- package/dist/sessions/auto-title.js.map +1 -1
- package/dist/sessions/manager.d.ts +1 -2
- package/dist/sessions/manager.d.ts.map +1 -1
- package/dist/sessions/manager.js +11 -36
- package/dist/sessions/manager.js.map +1 -1
- package/dist/sessions/manager.test.js +2 -10
- package/dist/sessions/manager.test.js.map +1 -1
- package/dist/storage/file-store.d.ts.map +1 -1
- package/dist/storage/file-store.js +2 -6
- package/dist/storage/file-store.js.map +1 -1
- package/dist/storage/file-store.test.js +1 -6
- package/dist/storage/file-store.test.js.map +1 -1
- package/dist/storage/project-store.d.ts.map +1 -1
- package/dist/storage/project-store.js +12 -0
- package/dist/storage/project-store.js.map +1 -1
- package/dist/storage/runtime-settings.d.ts +25 -1
- package/dist/storage/runtime-settings.d.ts.map +1 -1
- package/dist/storage/runtime-settings.js +31 -0
- package/dist/storage/runtime-settings.js.map +1 -1
- package/package.json +10 -5
- package/prompts/roles/TITLE.en.md +30 -0
- package/prompts/security/ask-protected-path.en.md +1 -0
- package/prompts/security/ask-protected-path.ru.md +1 -0
- package/prompts/security/deny-command.en.md +1 -0
- package/prompts/security/deny-command.ru.md +1 -0
- package/prompts/security/deny-protected-dir.en.md +1 -0
- package/prompts/security/deny-protected-dir.ru.md +1 -0
- package/prompts/security/deny-protected-path.en.md +1 -0
- package/prompts/security/deny-protected-path.ru.md +1 -0
- package/prompts/subagents/explore.en.md +23 -0
- package/prompts/subagents/general.en.md +19 -0
- package/prompts/system/MODE_ACT.en.md +20 -0
- package/{dist/prompts/modes/act.md → prompts/system/MODE_ACT.ru.md} +2 -0
- package/prompts/system/MODE_PLAN.en.md +22 -0
- package/{dist/prompts/prompts/modes/plan.md → prompts/system/MODE_PLAN.ru.md} +4 -3
- package/prompts/system/MODE_YOLO.en.md +22 -0
- package/{dist/prompts/prompts/modes/yolo.md → prompts/system/MODE_YOLO.ru.md} +2 -0
- package/prompts/system/PLAN_GUIDE.en.md +36 -0
- package/prompts/system/PLAN_GUIDE.ru.md +33 -0
- package/prompts/system/PLAN_MODE_AUTO.en.md +11 -0
- package/prompts/system/PLAN_MODE_AUTO.ru.md +12 -0
- package/prompts/system/PLAN_MODE_STRICT_APPROVED.en.md +8 -0
- package/prompts/system/PLAN_MODE_STRICT_APPROVED.ru.md +8 -0
- package/prompts/system/PLAN_MODE_STRICT_NOT_APPROVED.en.md +7 -0
- package/prompts/system/PLAN_MODE_STRICT_NOT_APPROVED.ru.md +7 -0
- package/dist/dictionaries/dictionaries/coding.en.yaml +0 -20
- package/dist/dictionaries/dictionaries/coding.ru.yaml +0 -22
- package/dist/dictionaries/dictionaries/coding.tools.yaml +0 -12
- package/dist/dictionaries/dictionaries/enum.en.yaml +0 -54
- package/dist/dictionaries/dictionaries/enum.ru.yaml +0 -67
- package/dist/dictionaries/dictionaries/injection.en.yaml +0 -51
- package/dist/dictionaries/dictionaries/injection.ru.yaml +0 -51
- package/dist/dictionaries/dictionaries/intents.en.yaml +0 -68
- package/dist/dictionaries/dictionaries/intents.ru.yaml +0 -78
- package/dist/dictionaries/dictionaries/research.en.yaml +0 -17
- package/dist/dictionaries/dictionaries/research.ru.yaml +0 -17
- package/dist/dictionaries/dictionaries/small-talk.en.yaml +0 -67
- package/dist/dictionaries/dictionaries/small-talk.ru.yaml +0 -81
- package/dist/prompts/modes/yolo.md +0 -20
- package/dist/prompts/prompts/modes/act.md +0 -18
- package/dist/prompts/prompts/roles/ADMIN.en.md +0 -33
- package/dist/prompts/prompts/roles/ADMIN.ru.md +0 -33
- package/dist/prompts/prompts/roles/ANALYST.en.md +0 -33
- package/dist/prompts/prompts/roles/ANALYST.ru.md +0 -33
- package/dist/prompts/prompts/roles/ASSISTANT.en.md +0 -38
- package/dist/prompts/prompts/roles/ASSISTANT.ru.md +0 -38
- package/dist/prompts/prompts/roles/AUDITOR.en.md +0 -33
- package/dist/prompts/prompts/roles/AUDITOR.ru.md +0 -33
- package/dist/prompts/prompts/roles/DEVELOPER.en.md +0 -42
- package/dist/prompts/prompts/roles/DEVELOPER.ru.md +0 -42
- package/dist/prompts/prompts/roles/DEVOPS.en.md +0 -35
- package/dist/prompts/prompts/roles/DEVOPS.ru.md +0 -35
- package/dist/prompts/prompts/roles/PM.en.md +0 -33
- package/dist/prompts/prompts/roles/PM.ru.md +0 -33
- package/dist/prompts/prompts/roles/QA.en.md +0 -33
- package/dist/prompts/prompts/roles/QA.ru.md +0 -33
- package/dist/prompts/prompts/roles/TITLE.ru.md +0 -30
- package/dist/prompts/prompts/subagents/explore.ru.md +0 -23
- package/dist/prompts/prompts/subagents/general.ru.md +0 -19
- package/dist/prompts/prompts/system/COMPRESSED_DIALOG.en.md +0 -5
- package/dist/prompts/prompts/system/COMPRESSED_DIALOG.ru.md +0 -5
- package/dist/prompts/prompts/system/INTERVIEW.en.md +0 -18
- package/dist/prompts/prompts/system/INTERVIEW.ru.md +0 -18
- package/dist/prompts/prompts/system/PARTICIPANTS_NOTICE.en.md +0 -7
- package/dist/prompts/prompts/system/PARTICIPANTS_NOTICE.ru.md +0 -7
- package/dist/prompts/prompts/system/ROLES_DESCRIPTION.en.md +0 -16
- package/dist/prompts/prompts/system/ROLES_DESCRIPTION.ru.md +0 -16
- package/dist/prompts/prompts/system/ROUTING.en.md +0 -13
- package/dist/prompts/prompts/system/ROUTING.md +0 -6
- package/dist/prompts/prompts/system/ROUTING.ru.md +0 -13
- package/dist/prompts/prompts/system/RULES.en.md +0 -10
- package/dist/prompts/prompts/system/RULES.md +0 -10
- package/dist/prompts/prompts/system/RULES.ru.md +0 -10
- package/dist/prompts/prompts/system/STICKY_NOTES.en.md +0 -6
- package/dist/prompts/prompts/system/STICKY_NOTES.ru.md +0 -6
- package/dist/prompts/prompts/system/SUMMARIZER.en.md +0 -54
- package/dist/prompts/prompts/system/SUMMARIZER.ru.md +0 -54
- package/dist/prompts/prompts/system/USER_CONTEXT.en.md +0 -5
- package/dist/prompts/prompts/system/USER_CONTEXT.ru.md +0 -5
- package/dist/prompts/system/ROUTING.md +0 -6
- package/dist/prompts/system/RULES.md +0 -10
- package/dist/server/routes/i18n.d.ts +0 -3
- package/dist/server/routes/i18n.d.ts.map +0 -1
- package/dist/server/routes/i18n.js +0 -83
- package/dist/server/routes/i18n.js.map +0 -1
- package/prompts/modes/plan.md +0 -20
- package/prompts/system/ROUTING.md +0 -6
- package/prompts/system/RULES.md +0 -10
- /package/dist/prompts/{prompts/system → system}/GATEWAY.en.md +0 -0
- /package/dist/prompts/{prompts/system → system}/GATEWAY.ru.md +0 -0
|
@@ -0,0 +1,674 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
2
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { WebSocket } from 'ws';
|
|
6
|
+
import { createStandaloneApp, injectRemoteProvider } from './app.js';
|
|
7
|
+
import { createLocalAuth } from '../auth/local.js';
|
|
8
|
+
import { JsonStore } from '../storage/json-store.js';
|
|
9
|
+
import { clearRemoteTokenProviders } from '../providers/remote.js';
|
|
10
|
+
const REMOTE_PROVIDER = {
|
|
11
|
+
name: 'itphx',
|
|
12
|
+
type: 'remote',
|
|
13
|
+
baseUrl: 'https://backend.test',
|
|
14
|
+
apiKey: '',
|
|
15
|
+
enabled: true,
|
|
16
|
+
};
|
|
17
|
+
const BACKEND_URL = 'https://backend.test';
|
|
18
|
+
const SERVER_USER = { id: 'u-1', username: 'lucky', roles: ['USER'] };
|
|
19
|
+
function jsonResponse(body, status = 200, headers = {}) {
|
|
20
|
+
return new Response(JSON.stringify(body), {
|
|
21
|
+
status,
|
|
22
|
+
headers: { 'Content-Type': 'application/json', ...headers },
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/** Что реальный backend отдаёт в GET /api/v2/providers (включённый itphx). */
|
|
26
|
+
function defaultProvidersBody() {
|
|
27
|
+
return {
|
|
28
|
+
providers: [
|
|
29
|
+
{
|
|
30
|
+
name: 'itphx', type: 'openai', url: 'https://ai.itphx.ru/v1',
|
|
31
|
+
model: 'm1', enabled: true, is_default: true,
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
default_provider: 'itphx',
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* fetch-роутер runtime-подключения: /auth/status (пользователь), /api/v2/providers
|
|
39
|
+
* (зеркалируемые провайдеры) и /api/v2/models (модели сервера) нужны самому
|
|
40
|
+
* connectRemoteWithApiKey+applyConnected; остальные URL уходят в onOther.
|
|
41
|
+
*/
|
|
42
|
+
function hybridFetchMock(opts) {
|
|
43
|
+
const { modelsBody, providersBody, onOther } = opts;
|
|
44
|
+
return vi.fn((url, init) => {
|
|
45
|
+
const u = String(url);
|
|
46
|
+
if (u.includes('/auth/status')) {
|
|
47
|
+
return Promise.resolve(jsonResponse({ authenticated: true, oidc_enabled: true, user: SERVER_USER }));
|
|
48
|
+
}
|
|
49
|
+
if (u.includes('/api/v2/providers')) {
|
|
50
|
+
return Promise.resolve(jsonResponse(providersBody ?? defaultProvidersBody()));
|
|
51
|
+
}
|
|
52
|
+
if (u.includes('/api/v2/models')) {
|
|
53
|
+
return Promise.resolve(jsonResponse(modelsBody ?? { groups: [] }));
|
|
54
|
+
}
|
|
55
|
+
return Promise.resolve(onOther ? onOther(u, init) : jsonResponse({ error: 'not_found' }, 404));
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function modelsBodyWith(remoteModels) {
|
|
59
|
+
return {
|
|
60
|
+
groups: [
|
|
61
|
+
{ provider: { name: 'itphx', type: 'openai' }, models: remoteModels },
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
afterEach(() => {
|
|
66
|
+
clearRemoteTokenProviders();
|
|
67
|
+
vi.unstubAllGlobals();
|
|
68
|
+
vi.restoreAllMocks();
|
|
69
|
+
});
|
|
70
|
+
describe('injectRemoteProvider (unit)', () => {
|
|
71
|
+
it('добавляет провайдера и модели в кэши, возвращает имя remote-провайдера', () => {
|
|
72
|
+
const providerCache = new Map();
|
|
73
|
+
const modelCache = new Map();
|
|
74
|
+
const name = injectRemoteProvider(providerCache, modelCache, {
|
|
75
|
+
provider: REMOTE_PROVIDER,
|
|
76
|
+
models: [
|
|
77
|
+
{ id: 'remote/m1', name: 'm1', provider: 'itphx', enabled: true },
|
|
78
|
+
{ id: 'remote/m2', name: 'm2', provider: 'itphx', enabled: true },
|
|
79
|
+
],
|
|
80
|
+
getToken: async () => 'tok',
|
|
81
|
+
});
|
|
82
|
+
expect(name).toBe('itphx');
|
|
83
|
+
expect(providerCache.get('itphx')).toMatchObject({
|
|
84
|
+
type: 'remote',
|
|
85
|
+
baseUrl: 'https://backend.test',
|
|
86
|
+
enabled: true,
|
|
87
|
+
});
|
|
88
|
+
expect(modelCache.get('remote/m1')).toBeDefined();
|
|
89
|
+
expect(modelCache.get('remote/m2')).toBeDefined();
|
|
90
|
+
});
|
|
91
|
+
it('без remoteProvider — ничего не меняет', () => {
|
|
92
|
+
const providerCache = new Map();
|
|
93
|
+
const modelCache = new Map();
|
|
94
|
+
expect(injectRemoteProvider(providerCache, modelCache, undefined)).toBeUndefined();
|
|
95
|
+
expect(providerCache.size).toBe(0);
|
|
96
|
+
expect(modelCache.size).toBe(0);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
describe('createStandaloneApp: remote-модели через runtime-подключение', () => {
|
|
100
|
+
it('после connectRemoteWithApiKey GET /api/v2/models форвардится на backend (прокси); в JsonStore ничего не пишется', async () => {
|
|
101
|
+
const dir = await mkdtemp(join(tmpdir(), 'synth-app-remote-'));
|
|
102
|
+
try {
|
|
103
|
+
const fetchMock = hybridFetchMock({
|
|
104
|
+
modelsBody: modelsBodyWith([
|
|
105
|
+
{ id: 'remote/m1', name: 'm1', provider: 'itphx', description: '', enabled: true },
|
|
106
|
+
]),
|
|
107
|
+
});
|
|
108
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
109
|
+
const handle = await createStandaloneApp({
|
|
110
|
+
port: 0,
|
|
111
|
+
dataDir: dir,
|
|
112
|
+
apiKey: 'test-key',
|
|
113
|
+
defaultBackendUrl: BACKEND_URL,
|
|
114
|
+
remoteLlm: 'server',
|
|
115
|
+
});
|
|
116
|
+
await handle.connectRemoteWithApiKey(BACKEND_URL, 'tok');
|
|
117
|
+
const res = await handle.app.request('/api/v2/models', { headers: { Authorization: 'Bearer test-key' } });
|
|
118
|
+
expect(res.status).toBe(200);
|
|
119
|
+
const body = (await res.json());
|
|
120
|
+
const group = body.groups.find((g) => g.provider.name === 'itphx');
|
|
121
|
+
expect(group).toBeDefined();
|
|
122
|
+
expect(group.models.map((m) => m.id)).toEqual(['remote/m1']);
|
|
123
|
+
// Инъекция — только in-memory: в store ничего не записано.
|
|
124
|
+
const modelStore = new JsonStore({ dataDir: dir, subdir: 'models' });
|
|
125
|
+
expect(await modelStore.exists('remote/m1')).toBe(false);
|
|
126
|
+
const providerStore = new JsonStore({ dataDir: dir, subdir: 'providers' });
|
|
127
|
+
expect(await providerStore.exists('itphx')).toBe(false);
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
await rm(dir, { recursive: true, force: true }).catch(() => { });
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
it('без подключения — поведение не изменилось (remote-группы нет)', async () => {
|
|
134
|
+
const dir = await mkdtemp(join(tmpdir(), 'synth-app-remote-none-'));
|
|
135
|
+
try {
|
|
136
|
+
const { app } = await createStandaloneApp({ port: 0, dataDir: dir, apiKey: 'test-key' });
|
|
137
|
+
const res = await app.request('/api/v2/models', { headers: { Authorization: 'Bearer test-key' } });
|
|
138
|
+
expect(res.status).toBe(200);
|
|
139
|
+
const body = (await res.json());
|
|
140
|
+
expect(body.groups.find((g) => g.provider.name === 'itphx')).toBeUndefined();
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
await rm(dir, { recursive: true, force: true }).catch(() => { });
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
it('disconnectRemote убирает remote-провайдера/модели и сбрасывает гибрид-статус', async () => {
|
|
147
|
+
const dir = await mkdtemp(join(tmpdir(), 'synth-app-remote-disc-'));
|
|
148
|
+
try {
|
|
149
|
+
const fetchMock = hybridFetchMock({
|
|
150
|
+
modelsBody: modelsBodyWith([
|
|
151
|
+
{ id: 'remote/m1', name: 'm1', provider: 'itphx', description: '', enabled: true },
|
|
152
|
+
]),
|
|
153
|
+
});
|
|
154
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
155
|
+
const handle = await createStandaloneApp({
|
|
156
|
+
port: 0,
|
|
157
|
+
dataDir: dir,
|
|
158
|
+
apiKey: 'test-key',
|
|
159
|
+
defaultBackendUrl: BACKEND_URL,
|
|
160
|
+
remoteLlm: 'server',
|
|
161
|
+
});
|
|
162
|
+
await handle.connectRemoteWithApiKey(BACKEND_URL, 'tok');
|
|
163
|
+
expect(handle.getHybridStatus().connected).toBe(true);
|
|
164
|
+
await handle.disconnectRemote();
|
|
165
|
+
expect(handle.getHybridStatus().connected).toBe(false);
|
|
166
|
+
expect(handle.getHybridStatus().backendUrl).toBeNull();
|
|
167
|
+
const res = await handle.app.request('/api/v2/models', { headers: { Authorization: 'Bearer test-key' } });
|
|
168
|
+
const body = (await res.json());
|
|
169
|
+
expect(body.groups.find((g) => g.provider.name === 'itphx')).toBeUndefined();
|
|
170
|
+
}
|
|
171
|
+
finally {
|
|
172
|
+
await rm(dir, { recursive: true, force: true }).catch(() => { });
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
it('disconnectRemote восстанавливает локального провайдера, перекрытого зеркалом', async () => {
|
|
176
|
+
const dir = await mkdtemp(join(tmpdir(), 'synth-app-remote-restore-'));
|
|
177
|
+
try {
|
|
178
|
+
// Локальный провайдер itphx (openai) + модель — конфликтует с серверным зеркалом.
|
|
179
|
+
const providerStore = new JsonStore({ dataDir: dir, subdir: 'providers' });
|
|
180
|
+
await providerStore.save('itphx', {
|
|
181
|
+
name: 'itphx', type: 'openai', baseUrl: 'http://local.test', apiKey: 'k', enabled: true,
|
|
182
|
+
});
|
|
183
|
+
const fetchMock = hybridFetchMock({
|
|
184
|
+
modelsBody: modelsBodyWith([
|
|
185
|
+
{ id: 'itphx/m1', name: 'm1', provider: 'itphx', description: '', enabled: true },
|
|
186
|
+
]),
|
|
187
|
+
});
|
|
188
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
189
|
+
const handle = await createStandaloneApp({
|
|
190
|
+
port: 0,
|
|
191
|
+
dataDir: dir,
|
|
192
|
+
apiKey: 'test-key',
|
|
193
|
+
defaultBackendUrl: BACKEND_URL,
|
|
194
|
+
remoteLlm: 'server',
|
|
195
|
+
});
|
|
196
|
+
// До подключения в providerCache локальный itphx.
|
|
197
|
+
await handle.connectRemoteWithApiKey(BACKEND_URL, 'tok');
|
|
198
|
+
await handle.disconnectRemote();
|
|
199
|
+
// Локальная сессия с itphx после отключения резолвится (зеркало откачено):
|
|
200
|
+
// GET /api/v2/models не проксируется, локальная группа itphx снова видна.
|
|
201
|
+
const res = await handle.app.request('/api/v2/models', { headers: { Authorization: 'Bearer test-key' } });
|
|
202
|
+
const body = (await res.json());
|
|
203
|
+
const localGroup = body.groups.find((g) => g.provider.name === 'itphx');
|
|
204
|
+
expect(localGroup).toBeDefined();
|
|
205
|
+
expect(localGroup.provider.type).toBe('openai');
|
|
206
|
+
}
|
|
207
|
+
finally {
|
|
208
|
+
await rm(dir, { recursive: true, force: true }).catch(() => { });
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
describe('createStandaloneApp: зеркалирование серверных провайдеров (локальная сессия → LLM Proxy)', () => {
|
|
213
|
+
function sseResponse(events) {
|
|
214
|
+
return new Response(events.map((e) => `data: ${e}\n\n`).join(''), {
|
|
215
|
+
status: 200,
|
|
216
|
+
headers: { 'Content-Type': 'text/event-stream' },
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
it('локальная сессия резолвит зеркало itphx и шлёт запрос с provider в теле', { timeout: 20_000 }, async () => {
|
|
220
|
+
const dir = await mkdtemp(join(tmpdir(), 'synth-app-mirror-e2e-'));
|
|
221
|
+
try {
|
|
222
|
+
const llmProxyCalls = [];
|
|
223
|
+
const fetchMock = hybridFetchMock({
|
|
224
|
+
modelsBody: modelsBodyWith([
|
|
225
|
+
{
|
|
226
|
+
id: 'itphx/m1', name: 'm1', provider: 'itphx', description: '', enabled: true,
|
|
227
|
+
contextWindow: 16_384, inputPrice: 1, outputPrice: 2,
|
|
228
|
+
},
|
|
229
|
+
]),
|
|
230
|
+
onOther: (url, init) => {
|
|
231
|
+
if (url.includes('/api/v2/llm/chat/completions')) {
|
|
232
|
+
const rawBody = typeof init?.body === 'string' ? init.body : '';
|
|
233
|
+
llmProxyCalls.push({ url, body: JSON.parse(rawBody) });
|
|
234
|
+
return sseResponse([
|
|
235
|
+
JSON.stringify({ choices: [{ index: 0, delta: { content: 'Ответ зеркала itphx' }, finish_reason: null }] }),
|
|
236
|
+
JSON.stringify({ choices: [{ index: 0, delta: {}, finish_reason: 'stop' }], usage: { prompt_tokens: 5, completion_tokens: 3, total_tokens: 8 } }),
|
|
237
|
+
'[DONE]',
|
|
238
|
+
]);
|
|
239
|
+
}
|
|
240
|
+
return jsonResponse({}, 200);
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
244
|
+
const handle = await createStandaloneApp({
|
|
245
|
+
port: 0,
|
|
246
|
+
dataDir: dir,
|
|
247
|
+
apiKey: 'test-key',
|
|
248
|
+
defaultBackendUrl: BACKEND_URL,
|
|
249
|
+
remoteLlm: 'server',
|
|
250
|
+
});
|
|
251
|
+
await handle.connectRemoteWithApiKey(BACKEND_URL, 'tok');
|
|
252
|
+
// Локальная сессия с провайдером/моделью сервера (имя зеркала = itphx).
|
|
253
|
+
const createRes = await handle.app.request('/api/v2/sessions', {
|
|
254
|
+
method: 'POST',
|
|
255
|
+
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer test-key' },
|
|
256
|
+
body: JSON.stringify({
|
|
257
|
+
name: 'зеркало itphx',
|
|
258
|
+
settings: { provider: 'itphx', model: 'itphx/m1' },
|
|
259
|
+
}),
|
|
260
|
+
});
|
|
261
|
+
expect(createRes.status).toBe(201);
|
|
262
|
+
const ses = (await createRes.json());
|
|
263
|
+
expect(ses.id).toBeDefined();
|
|
264
|
+
const promptRes = await handle.app.request(`/api/v2/sessions/${ses.id}/prompt`, {
|
|
265
|
+
method: 'POST',
|
|
266
|
+
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer test-key' },
|
|
267
|
+
body: JSON.stringify({ content: 'привет зеркалу' }),
|
|
268
|
+
});
|
|
269
|
+
expect(promptRes.status).toBe(200);
|
|
270
|
+
const text = await promptRes.text();
|
|
271
|
+
const events = text.split('\n')
|
|
272
|
+
.filter((l) => l.startsWith('data: '))
|
|
273
|
+
.map((l) => JSON.parse(l.slice(6)));
|
|
274
|
+
const done = events.find((e) => e.type === 'stream.done');
|
|
275
|
+
expect(done).toBeDefined();
|
|
276
|
+
expect(String(done.data.content)).toContain('Ответ зеркала itphx');
|
|
277
|
+
// RemoteProvider ушёл на сервер с ЯВНЫМ provider (имя зеркала) и моделью сервера.
|
|
278
|
+
const proxyCall = llmProxyCalls.find((c) => c.url.includes('/api/v2/llm/chat/completions'));
|
|
279
|
+
expect(proxyCall).toBeDefined();
|
|
280
|
+
expect(proxyCall.body.provider).toBe('itphx');
|
|
281
|
+
expect(proxyCall.body.model).toBe('itphx/m1');
|
|
282
|
+
}
|
|
283
|
+
finally {
|
|
284
|
+
await rm(dir, { recursive: true, force: true }).catch(() => { });
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
it('remote.llm=local: зеркало не перекрывает локального провайдера с тем же именем', { timeout: 20_000 }, async () => {
|
|
288
|
+
const dir = await mkdtemp(join(tmpdir(), 'synth-app-mirror-local-'));
|
|
289
|
+
try {
|
|
290
|
+
// Локальный провайдер itphx (openai) — при remote.llm=local он не перекрывается.
|
|
291
|
+
const providerStore = new JsonStore({ dataDir: dir, subdir: 'providers' });
|
|
292
|
+
await providerStore.save('itphx', {
|
|
293
|
+
name: 'itphx', type: 'openai', baseUrl: 'http://local.test', apiKey: 'k', enabled: true,
|
|
294
|
+
});
|
|
295
|
+
const fetchMock = hybridFetchMock({
|
|
296
|
+
modelsBody: modelsBodyWith([
|
|
297
|
+
{ id: 'itphx/m1', name: 'm1', provider: 'itphx', description: '', enabled: true },
|
|
298
|
+
]),
|
|
299
|
+
});
|
|
300
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
301
|
+
const handle = await createStandaloneApp({
|
|
302
|
+
port: 0,
|
|
303
|
+
dataDir: dir,
|
|
304
|
+
apiKey: 'test-key',
|
|
305
|
+
defaultBackendUrl: BACKEND_URL,
|
|
306
|
+
remoteLlm: 'local',
|
|
307
|
+
});
|
|
308
|
+
await handle.connectRemoteWithApiKey(BACKEND_URL, 'tok');
|
|
309
|
+
// Локальная сессия всё ещё резолвит ЛОКАЛЬНЫЙ itphx (openai) — зеркало не создано.
|
|
310
|
+
const createRes = await handle.app.request('/api/v2/sessions', {
|
|
311
|
+
method: 'POST',
|
|
312
|
+
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer test-key' },
|
|
313
|
+
body: JSON.stringify({
|
|
314
|
+
name: 'локальный itphx',
|
|
315
|
+
settings: { provider: 'itphx', model: 'itphx/m1' },
|
|
316
|
+
}),
|
|
317
|
+
});
|
|
318
|
+
expect(createRes.status).toBe(201);
|
|
319
|
+
// В remote.llm=local моделей сервера для itphx нет (провайдер перекрыт
|
|
320
|
+
// локальным) — зеркало не появилось. После отключения локальная группа
|
|
321
|
+
// itphx (openai) видна как раньше.
|
|
322
|
+
await handle.disconnectRemote();
|
|
323
|
+
const res = await handle.app.request('/api/v2/models', { headers: { Authorization: 'Bearer test-key' } });
|
|
324
|
+
const body = (await res.json());
|
|
325
|
+
const group = body.groups.find((g) => g.provider.name === 'itphx');
|
|
326
|
+
expect(group).toBeDefined();
|
|
327
|
+
expect(group.provider.type).toBe('openai');
|
|
328
|
+
}
|
|
329
|
+
finally {
|
|
330
|
+
await rm(dir, { recursive: true, force: true }).catch(() => { });
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
describe('createStandaloneApp: RemoteEventBridge и /api/v2/events (runtime)', () => {
|
|
335
|
+
it('после подключения /api/v2/events доставляет remote:<id> через subscribeAll', async () => {
|
|
336
|
+
const dir = await mkdtemp(join(tmpdir(), 'synth-app-bridge-'));
|
|
337
|
+
const abort = new AbortController();
|
|
338
|
+
try {
|
|
339
|
+
const fetchMock = hybridFetchMock({
|
|
340
|
+
modelsBody: modelsBodyWith([]),
|
|
341
|
+
// SSE-эндпоинт backend: стрим с одним событием и закрытием.
|
|
342
|
+
onOther: () => jsonResponse({}, 200),
|
|
343
|
+
});
|
|
344
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
345
|
+
const handle = await createStandaloneApp({
|
|
346
|
+
port: 0,
|
|
347
|
+
dataDir: dir,
|
|
348
|
+
apiKey: 'test-key',
|
|
349
|
+
defaultBackendUrl: BACKEND_URL,
|
|
350
|
+
});
|
|
351
|
+
await handle.connectRemoteWithApiKey(BACKEND_URL, 'tok');
|
|
352
|
+
// Мост создан и запущен при подключении.
|
|
353
|
+
expect(handle.remoteEventBridge).toBeDefined();
|
|
354
|
+
const res = await handle.app.request('/api/v2/events', {
|
|
355
|
+
headers: { Authorization: 'Bearer test-key' },
|
|
356
|
+
signal: abort.signal,
|
|
357
|
+
});
|
|
358
|
+
expect(res.status).toBe(200);
|
|
359
|
+
const reader = res.body.getReader();
|
|
360
|
+
const decoder = new TextDecoder();
|
|
361
|
+
// То, что сделал бы мост: публикация события удалённой сессии (remote:<id>)
|
|
362
|
+
// и локальной сессии — обе доходят до UI через subscribeAll.
|
|
363
|
+
handle.eventBus.publish('remote:ses-1', 'session.updated', { id: 'remote:ses-1', name: 'x' });
|
|
364
|
+
handle.eventBus.publish('local-1', 'stream.chunk', { stream_id: 's1' });
|
|
365
|
+
const first = await reader.read();
|
|
366
|
+
expect(decoder.decode(first.value)).toContain('"sessionId":"remote:ses-1"');
|
|
367
|
+
const second = await reader.read();
|
|
368
|
+
expect(decoder.decode(second.value)).toContain('"sessionId":"local-1"');
|
|
369
|
+
}
|
|
370
|
+
finally {
|
|
371
|
+
abort.abort();
|
|
372
|
+
await rm(dir, { recursive: true, force: true }).catch(() => { });
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
describe('createStandaloneApp: WS /ws/events send_message с remote-рефом (runtime)', () => {
|
|
377
|
+
/**
|
|
378
|
+
* Открыть WS и начать собирать сообщения ДО установки соединения: сервер
|
|
379
|
+
* шлёт 'connected' сразу после upgrade, поэтому listener вешаем заранее.
|
|
380
|
+
*/
|
|
381
|
+
function connectCollecting(port, apiKey) {
|
|
382
|
+
const received = [];
|
|
383
|
+
const ws = new WebSocket(`ws://127.0.0.1:${port}/ws/events`, {
|
|
384
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
385
|
+
});
|
|
386
|
+
ws.on('message', (raw) => {
|
|
387
|
+
try {
|
|
388
|
+
received.push(JSON.parse(raw.toString()));
|
|
389
|
+
}
|
|
390
|
+
catch { /* ignore */ }
|
|
391
|
+
});
|
|
392
|
+
const opened = new Promise((resolve, reject) => {
|
|
393
|
+
ws.on('open', () => resolve(ws));
|
|
394
|
+
ws.on('error', reject);
|
|
395
|
+
});
|
|
396
|
+
return { ws: opened, received };
|
|
397
|
+
}
|
|
398
|
+
async function startServer(config = {}) {
|
|
399
|
+
const dir = await mkdtemp(join(tmpdir(), 'synth-app-ws-remote-'));
|
|
400
|
+
const handle = await createStandaloneApp({
|
|
401
|
+
port: 0,
|
|
402
|
+
dataDir: dir,
|
|
403
|
+
apiKey: 'test-key',
|
|
404
|
+
...config,
|
|
405
|
+
});
|
|
406
|
+
await new Promise((resolve) => handle.server.listen(0, resolve));
|
|
407
|
+
const address = handle.server.address();
|
|
408
|
+
const port = typeof address === 'object' && address ? address.port : 0;
|
|
409
|
+
return {
|
|
410
|
+
dir,
|
|
411
|
+
handle,
|
|
412
|
+
server: handle.server,
|
|
413
|
+
port,
|
|
414
|
+
close: async () => {
|
|
415
|
+
await handle.disconnectRemote?.().catch(() => { });
|
|
416
|
+
await new Promise((resolve) => handle.server.close(() => resolve()));
|
|
417
|
+
await rm(dir, { recursive: true, force: true }).catch(() => { });
|
|
418
|
+
},
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
it('remote:<id>: промпт уходит на backend (fetch), локальный оркестратор не трогается', async () => {
|
|
422
|
+
const fetchMock = hybridFetchMock({
|
|
423
|
+
modelsBody: modelsBodyWith([]),
|
|
424
|
+
onOther: (url, init) => {
|
|
425
|
+
if (url.includes('/prompt')) {
|
|
426
|
+
expect(init?.method).toBe('POST');
|
|
427
|
+
return jsonResponse({ ok: true });
|
|
428
|
+
}
|
|
429
|
+
return jsonResponse({}, 200);
|
|
430
|
+
},
|
|
431
|
+
});
|
|
432
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
433
|
+
const app = await startServer({ defaultBackendUrl: BACKEND_URL });
|
|
434
|
+
await app.handle.connectRemoteWithApiKey(BACKEND_URL, 'tok');
|
|
435
|
+
const { ws: wsP, received } = connectCollecting(app.port, 'test-key');
|
|
436
|
+
const ws = await wsP;
|
|
437
|
+
try {
|
|
438
|
+
await vi.waitFor(() => {
|
|
439
|
+
expect(received.some((m) => m.type === 'connected')).toBe(true);
|
|
440
|
+
}, { timeout: 5000 });
|
|
441
|
+
ws.send(JSON.stringify({
|
|
442
|
+
type: 'send_message',
|
|
443
|
+
session_id: 'remote:ses-1',
|
|
444
|
+
content: 'привет удалённой сессии',
|
|
445
|
+
agent_role: 'developer',
|
|
446
|
+
model: 'remote/m1',
|
|
447
|
+
}));
|
|
448
|
+
await vi.waitFor(() => {
|
|
449
|
+
const promptCall = fetchMock.mock.calls.find(([u]) => String(u).includes('/api/v2/sessions/ses-1/prompt'));
|
|
450
|
+
expect(promptCall).toBeDefined();
|
|
451
|
+
}, { timeout: 5000 });
|
|
452
|
+
// Промпт ушёл на backend с локальным id (без префикса) и текстом.
|
|
453
|
+
const promptCall = fetchMock.mock.calls.find(([u]) => String(u).includes('/api/v2/sessions/ses-1/prompt'));
|
|
454
|
+
const [url, init] = promptCall;
|
|
455
|
+
expect(String(url)).toBe(`${BACKEND_URL}/api/v2/sessions/ses-1/prompt`);
|
|
456
|
+
const rawBody = init?.body;
|
|
457
|
+
const sentBody = typeof rawBody === 'string'
|
|
458
|
+
? JSON.parse(rawBody)
|
|
459
|
+
: JSON.parse(new TextDecoder().decode(rawBody));
|
|
460
|
+
expect(sentBody.content).toBe('привет удалённой сессии');
|
|
461
|
+
expect(sentBody.agent).toBe('developer');
|
|
462
|
+
expect(sentBody.model).toBe('remote/m1');
|
|
463
|
+
// UI сначала получил queued, ошибки «Session not found» нет.
|
|
464
|
+
await vi.waitFor(() => {
|
|
465
|
+
expect(received.some((m) => m.type === 'queued' && m.session_id === 'remote:ses-1')).toBe(true);
|
|
466
|
+
}, { timeout: 5000 });
|
|
467
|
+
expect(received.some((m) => m.type === 'error')).toBe(false);
|
|
468
|
+
}
|
|
469
|
+
finally {
|
|
470
|
+
ws.close();
|
|
471
|
+
await app.close();
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
it('remote:<id> без подключения → error «Удалённый сервер не подключён»', async () => {
|
|
475
|
+
const app = await startServer({});
|
|
476
|
+
const { ws: wsP, received } = connectCollecting(app.port, 'test-key');
|
|
477
|
+
const ws = await wsP;
|
|
478
|
+
try {
|
|
479
|
+
// Ждём handshake-сообщение connected: сервер зарегистрировал on('message').
|
|
480
|
+
await vi.waitFor(() => {
|
|
481
|
+
expect(received.some((m) => m.type === 'connected')).toBe(true);
|
|
482
|
+
}, { timeout: 5000 });
|
|
483
|
+
ws.send(JSON.stringify({
|
|
484
|
+
type: 'send_message',
|
|
485
|
+
session_id: 'remote:ses-1',
|
|
486
|
+
content: 'привет',
|
|
487
|
+
}));
|
|
488
|
+
await vi.waitFor(() => {
|
|
489
|
+
expect(received.some((m) => m.type === 'error' && m.data?.error === 'Удалённый сервер не подключён')).toBe(true);
|
|
490
|
+
}, { timeout: 5000 });
|
|
491
|
+
}
|
|
492
|
+
finally {
|
|
493
|
+
ws.close();
|
|
494
|
+
await app.close();
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
it('ошибка backend на prompt → WS error в формате локальных ошибок', async () => {
|
|
498
|
+
const fetchMock = hybridFetchMock({
|
|
499
|
+
modelsBody: modelsBodyWith([]),
|
|
500
|
+
onOther: (url) => {
|
|
501
|
+
if (url.includes('/prompt')) {
|
|
502
|
+
return jsonResponse({ error: { code: 'RATE_LIMITED', message: 'HTTP 429 (remote)' } }, 429);
|
|
503
|
+
}
|
|
504
|
+
return jsonResponse({}, 200);
|
|
505
|
+
},
|
|
506
|
+
});
|
|
507
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
508
|
+
const app = await startServer({ defaultBackendUrl: BACKEND_URL });
|
|
509
|
+
await app.handle.connectRemoteWithApiKey(BACKEND_URL, 'tok');
|
|
510
|
+
const { ws: wsP, received } = connectCollecting(app.port, 'test-key');
|
|
511
|
+
const ws = await wsP;
|
|
512
|
+
try {
|
|
513
|
+
await vi.waitFor(() => {
|
|
514
|
+
expect(received.some((m) => m.type === 'connected')).toBe(true);
|
|
515
|
+
}, { timeout: 5000 });
|
|
516
|
+
ws.send(JSON.stringify({
|
|
517
|
+
type: 'send_message',
|
|
518
|
+
session_id: 'remote:ses-1',
|
|
519
|
+
content: 'привет',
|
|
520
|
+
}));
|
|
521
|
+
await vi.waitFor(() => {
|
|
522
|
+
const hasHttp429 = received.some((m) => m.type === 'error'
|
|
523
|
+
&& String(m.data?.error).includes('HTTP 429'));
|
|
524
|
+
expect(hasHttp429).toBe(true);
|
|
525
|
+
}, { timeout: 5000 });
|
|
526
|
+
}
|
|
527
|
+
finally {
|
|
528
|
+
ws.close();
|
|
529
|
+
await app.close();
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
describe('createStandaloneApp: WS /ws/events аутентификация через cookie v2 (server-identity)', () => {
|
|
534
|
+
function collect(ws) {
|
|
535
|
+
const received = [];
|
|
536
|
+
ws.on('message', (raw) => {
|
|
537
|
+
try {
|
|
538
|
+
received.push(JSON.parse(raw.toString()));
|
|
539
|
+
}
|
|
540
|
+
catch { /* ignore */ }
|
|
541
|
+
});
|
|
542
|
+
return received;
|
|
543
|
+
}
|
|
544
|
+
async function startServer() {
|
|
545
|
+
const dir = await mkdtemp(join(tmpdir(), 'synth-app-ws-cookie-'));
|
|
546
|
+
const handle = await createStandaloneApp({
|
|
547
|
+
port: 0,
|
|
548
|
+
dataDir: dir,
|
|
549
|
+
apiKey: 'test-key',
|
|
550
|
+
});
|
|
551
|
+
await new Promise((resolve) => handle.server.listen(0, resolve));
|
|
552
|
+
const address = handle.server.address();
|
|
553
|
+
const port = typeof address === 'object' && address ? address.port : 0;
|
|
554
|
+
return {
|
|
555
|
+
dir,
|
|
556
|
+
handle,
|
|
557
|
+
server: handle.server,
|
|
558
|
+
port,
|
|
559
|
+
close: async () => {
|
|
560
|
+
await handle.disconnectRemote?.().catch(() => { });
|
|
561
|
+
await new Promise((resolve) => handle.server.close(() => resolve()));
|
|
562
|
+
await rm(dir, { recursive: true, force: true }).catch(() => { });
|
|
563
|
+
},
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
/** Собрать подписанную server-identity cookie v2 через setServerUserCookie. */
|
|
567
|
+
function makeServerCookie(apiKey, user) {
|
|
568
|
+
let setCookie = '';
|
|
569
|
+
const auth = createLocalAuth({ apiKey });
|
|
570
|
+
auth.setServerUserCookie({
|
|
571
|
+
req: { header: () => undefined },
|
|
572
|
+
header: (name, value) => {
|
|
573
|
+
if (name === 'Set-Cookie')
|
|
574
|
+
setCookie = value;
|
|
575
|
+
},
|
|
576
|
+
}, user);
|
|
577
|
+
return setCookie.split(';')[0];
|
|
578
|
+
}
|
|
579
|
+
it('server-identity cookie v2 → соединение принимается (connected)', async () => {
|
|
580
|
+
const app = await startServer();
|
|
581
|
+
const cookie = makeServerCookie('test-key', { id: 'u-1', username: 'lucky', roles: ['USER'] });
|
|
582
|
+
const ws = await new Promise((resolve, reject) => {
|
|
583
|
+
const s = new WebSocket(`ws://127.0.0.1:${app.port}/ws/events`, {
|
|
584
|
+
headers: { Cookie: cookie },
|
|
585
|
+
});
|
|
586
|
+
s.on('open', () => resolve(s));
|
|
587
|
+
s.on('error', reject);
|
|
588
|
+
});
|
|
589
|
+
const received = collect(ws);
|
|
590
|
+
try {
|
|
591
|
+
await vi.waitFor(() => {
|
|
592
|
+
expect(received.some((m) => m.type === 'connected')).toBe(true);
|
|
593
|
+
}, { timeout: 5000 });
|
|
594
|
+
}
|
|
595
|
+
finally {
|
|
596
|
+
ws.close();
|
|
597
|
+
await app.close();
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
it('Bearer API-ключ по-прежнему работает (CLI/API)', async () => {
|
|
601
|
+
const app = await startServer();
|
|
602
|
+
const ws = await new Promise((resolve, reject) => {
|
|
603
|
+
const s = new WebSocket(`ws://127.0.0.1:${app.port}/ws/events`, {
|
|
604
|
+
headers: { Authorization: 'Bearer test-key' },
|
|
605
|
+
});
|
|
606
|
+
s.on('open', () => resolve(s));
|
|
607
|
+
s.on('error', reject);
|
|
608
|
+
});
|
|
609
|
+
const received = collect(ws);
|
|
610
|
+
try {
|
|
611
|
+
await vi.waitFor(() => {
|
|
612
|
+
expect(received.some((m) => m.type === 'connected')).toBe(true);
|
|
613
|
+
}, { timeout: 5000 });
|
|
614
|
+
}
|
|
615
|
+
finally {
|
|
616
|
+
ws.close();
|
|
617
|
+
await app.close();
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
it('невалидная cookie / без авторизации → 401', async () => {
|
|
621
|
+
const app = await startServer();
|
|
622
|
+
try {
|
|
623
|
+
const result = await new Promise((resolve) => {
|
|
624
|
+
const s = new WebSocket(`ws://127.0.0.1:${app.port}/ws/events`, {
|
|
625
|
+
headers: { Cookie: 'synth_session=invalid' },
|
|
626
|
+
});
|
|
627
|
+
s.on('unexpected-response', (_req, res) => resolve({ status: res.statusCode }));
|
|
628
|
+
s.on('open', () => resolve({}));
|
|
629
|
+
s.on('error', () => resolve({}));
|
|
630
|
+
});
|
|
631
|
+
expect(result.status).toBe(401);
|
|
632
|
+
}
|
|
633
|
+
finally {
|
|
634
|
+
await app.close();
|
|
635
|
+
}
|
|
636
|
+
});
|
|
637
|
+
});
|
|
638
|
+
describe('createStandaloneApp: HTTP-эндпоинты переноса проектов смонтированы (publish/fork-local)', () => {
|
|
639
|
+
it('без runtime-подключения publish → 502 REMOTE_UNAVAILABLE', async () => {
|
|
640
|
+
const dir = await mkdtemp(join(tmpdir(), 'synth-app-transfer-mount-'));
|
|
641
|
+
try {
|
|
642
|
+
const { app } = await createStandaloneApp({ port: 0, dataDir: dir, apiKey: 'test-key' });
|
|
643
|
+
const res = await app.request('/api/v2/projects/local-proj/publish', {
|
|
644
|
+
method: 'POST',
|
|
645
|
+
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer test-key' },
|
|
646
|
+
body: JSON.stringify({}),
|
|
647
|
+
});
|
|
648
|
+
expect(res.status).toBe(502);
|
|
649
|
+
const body = (await res.json());
|
|
650
|
+
expect(body.error.code).toBe('REMOTE_UNAVAILABLE');
|
|
651
|
+
}
|
|
652
|
+
finally {
|
|
653
|
+
await rm(dir, { recursive: true, force: true }).catch(() => { });
|
|
654
|
+
}
|
|
655
|
+
});
|
|
656
|
+
it('без runtime-подключения fork-local → 502 REMOTE_UNAVAILABLE', async () => {
|
|
657
|
+
const dir = await mkdtemp(join(tmpdir(), 'synth-app-transfer-mount-'));
|
|
658
|
+
try {
|
|
659
|
+
const { app } = await createStandaloneApp({ port: 0, dataDir: dir, apiKey: 'test-key' });
|
|
660
|
+
const res = await app.request('/api/v2/projects/remote:abc/fork-local', {
|
|
661
|
+
method: 'POST',
|
|
662
|
+
headers: { 'Content-Type': 'application/json', Authorization: 'Bearer test-key' },
|
|
663
|
+
body: JSON.stringify({}),
|
|
664
|
+
});
|
|
665
|
+
expect(res.status).toBe(502);
|
|
666
|
+
const body = (await res.json());
|
|
667
|
+
expect(body.error.code).toBe('REMOTE_UNAVAILABLE');
|
|
668
|
+
}
|
|
669
|
+
finally {
|
|
670
|
+
await rm(dir, { recursive: true, force: true }).catch(() => { });
|
|
671
|
+
}
|
|
672
|
+
});
|
|
673
|
+
});
|
|
674
|
+
//# sourceMappingURL=app.remote.test.js.map
|