crewly 1.20.143 → 1.20.145
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/config/constants.ts +59 -0
- package/config/index.ts +2 -0
- package/config/roles/orchestrator/prompt.md +9 -0
- package/config/skills/orchestrator/delegate-task/execute.sh +11 -0
- package/config/skills/orchestrator/delegate-task/execute.test.sh +38 -0
- package/config/skills/orchestrator/harness-login/SKILL.md +88 -0
- package/config/skills/orchestrator/harness-login/execute.sh +75 -0
- package/config/skills/orchestrator/harness-login/execute.test.sh +72 -0
- package/config/skills/orchestrator/reply-chat/SKILL.md +11 -1
- package/config/skills/orchestrator/reply-chat/execute.sh +13 -1
- package/config/skills/orchestrator/reply-slack/SKILL.md +1 -0
- package/config/skills/orchestrator/reply-slack/execute.sh +11 -2
- package/dist/backend/backend/src/constants.d.ts +55 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +36 -1
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/chat/chat.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/chat/chat.controller.js +36 -1
- package/dist/backend/backend/src/controllers/chat/chat.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/harness/harness.routes.d.ts +1 -0
- package/dist/backend/backend/src/controllers/harness/harness.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/harness/harness.routes.js +3 -0
- package/dist/backend/backend/src/controllers/harness/harness.routes.js.map +1 -1
- package/dist/backend/backend/src/controllers/harness/owner-login.controller.d.ts +61 -0
- package/dist/backend/backend/src/controllers/harness/owner-login.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/harness/owner-login.controller.js +133 -0
- package/dist/backend/backend/src/controllers/harness/owner-login.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/slack/slack.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/slack/slack.controller.js +69 -7
- package/dist/backend/backend/src/controllers/slack/slack.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.d.ts +4 -1
- package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js +9 -2
- package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js.map +1 -1
- package/dist/backend/backend/src/index.d.ts +8 -0
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +75 -3
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.js +28 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/codex-runtime.service.d.ts +11 -1
- package/dist/backend/backend/src/services/agent/codex-runtime.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/codex-runtime.service.js +18 -0
- package/dist/backend/backend/src/services/agent/codex-runtime.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts +12 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js +18 -3
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.d.ts +29 -0
- package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.js +48 -0
- package/dist/backend/backend/src/services/agent/runtime-agent.service.abstract.js.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts +15 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.js +20 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.js.map +1 -1
- package/dist/backend/backend/src/services/harness/harness-relogin.service.d.ts +147 -5
- package/dist/backend/backend/src/services/harness/harness-relogin.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/harness/harness-relogin.service.js +210 -20
- package/dist/backend/backend/src/services/harness/harness-relogin.service.js.map +1 -1
- package/dist/backend/backend/src/services/harness/owner-login-request.d.ts +87 -0
- package/dist/backend/backend/src/services/harness/owner-login-request.d.ts.map +1 -0
- package/dist/backend/backend/src/services/harness/owner-login-request.js +191 -0
- package/dist/backend/backend/src/services/harness/owner-login-request.js.map +1 -0
- package/dist/backend/backend/src/services/orc/orc-reply-route.service.d.ts +217 -0
- package/dist/backend/backend/src/services/orc/orc-reply-route.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/orc/orc-reply-route.service.js +300 -0
- package/dist/backend/backend/src/services/orc/orc-reply-route.service.js.map +1 -0
- package/dist/backend/backend/src/services/reconciler/reconcile-rules.d.ts +32 -2
- package/dist/backend/backend/src/services/reconciler/reconcile-rules.d.ts.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconcile-rules.js +59 -5
- package/dist/backend/backend/src/services/reconciler/reconcile-rules.js.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts +4 -2
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js +6 -3
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-relogin-dm.service.d.ts +68 -9
- package/dist/backend/backend/src/services/slack/slack-relogin-dm.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-relogin-dm.service.js +121 -11
- package/dist/backend/backend/src/services/slack/slack-relogin-dm.service.js.map +1 -1
- package/dist/backend/backend/src/services/system/auto-update.service.d.ts +292 -0
- package/dist/backend/backend/src/services/system/auto-update.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/system/auto-update.service.js +590 -0
- package/dist/backend/backend/src/services/system/auto-update.service.js.map +1 -0
- package/dist/backend/backend/src/services/system/auto-update.utils.d.ts +265 -0
- package/dist/backend/backend/src/services/system/auto-update.utils.d.ts.map +1 -0
- package/dist/backend/backend/src/services/system/auto-update.utils.js +397 -0
- package/dist/backend/backend/src/services/system/auto-update.utils.js.map +1 -0
- package/dist/backend/backend/src/services/system/version-check.service.d.ts +15 -1
- package/dist/backend/backend/src/services/system/version-check.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/system/version-check.service.js +21 -2
- package/dist/backend/backend/src/services/system/version-check.service.js.map +1 -1
- package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts +7 -1
- package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/task-pool/task-pool.service.js +14 -2
- package/dist/backend/backend/src/services/task-pool/task-pool.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/cascade-request-status.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/cascade-request-status.js +16 -1
- package/dist/backend/backend/src/services/v3/cascade-request-status.js.map +1 -1
- package/dist/backend/backend/src/services/v3/request-completion.d.ts +128 -0
- package/dist/backend/backend/src/services/v3/request-completion.d.ts.map +1 -0
- package/dist/backend/backend/src/services/v3/request-completion.js +311 -0
- package/dist/backend/backend/src/services/v3/request-completion.js.map +1 -0
- package/dist/backend/backend/src/services/v3/request-decompose.subscriber.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/request-decompose.subscriber.js +3 -0
- package/dist/backend/backend/src/services/v3/request-decompose.subscriber.js.map +1 -1
- package/dist/backend/backend/src/services/v3/request-status-update.subscriber.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/request-status-update.subscriber.js +17 -3
- package/dist/backend/backend/src/services/v3/request-status-update.subscriber.js.map +1 -1
- package/dist/backend/backend/src/services/v3/v3-data.service.d.ts +8 -0
- package/dist/backend/backend/src/services/v3/v3-data.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/v3-data.service.js +11 -1
- package/dist/backend/backend/src/services/v3/v3-data.service.js.map +1 -1
- package/dist/backend/backend/src/types/settings.types.d.ts +8 -0
- package/dist/backend/backend/src/types/settings.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/settings.types.js +4 -0
- package/dist/backend/backend/src/types/settings.types.js.map +1 -1
- package/dist/backend/backend/src/utils/agent-reply-filter.utils.d.ts +75 -0
- package/dist/backend/backend/src/utils/agent-reply-filter.utils.d.ts.map +1 -0
- package/dist/backend/backend/src/utils/agent-reply-filter.utils.js +127 -0
- package/dist/backend/backend/src/utils/agent-reply-filter.utils.js.map +1 -0
- package/dist/backend/build-info.json +2 -2
- package/dist/backend/config/constants.d.ts +56 -0
- package/dist/backend/config/constants.d.ts.map +1 -1
- package/dist/backend/config/constants.js +57 -0
- package/dist/backend/config/constants.js.map +1 -1
- package/dist/backend/config/index.d.ts +2 -2
- package/dist/backend/config/index.d.ts.map +1 -1
- package/dist/backend/config/index.js +2 -2
- package/dist/backend/config/index.js.map +1 -1
- package/dist/cli/backend/src/constants.d.ts +55 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +36 -1
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/agent-registration.service.js +28 -1
- package/dist/cli/backend/src/services/agent/agent-registration.service.js.map +1 -1
- package/dist/cli/backend/src/services/agent/codex-runtime.service.d.ts +11 -1
- package/dist/cli/backend/src/services/agent/codex-runtime.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/codex-runtime.service.js +18 -0
- package/dist/cli/backend/src/services/agent/codex-runtime.service.js.map +1 -1
- package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts +12 -0
- package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js +18 -3
- package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js.map +1 -1
- package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.d.ts +29 -0
- package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.js +48 -0
- package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.js.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts +15 -0
- package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.js +20 -0
- package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.js.map +1 -1
- package/dist/cli/backend/src/services/harness/harness-relogin.service.d.ts +147 -5
- package/dist/cli/backend/src/services/harness/harness-relogin.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/harness/harness-relogin.service.js +210 -20
- package/dist/cli/backend/src/services/harness/harness-relogin.service.js.map +1 -1
- package/dist/cli/backend/src/services/harness/owner-login-request.d.ts +87 -0
- package/dist/cli/backend/src/services/harness/owner-login-request.d.ts.map +1 -0
- package/dist/cli/backend/src/services/harness/owner-login-request.js +191 -0
- package/dist/cli/backend/src/services/harness/owner-login-request.js.map +1 -0
- package/dist/cli/backend/src/services/orc/orc-reply-route.service.d.ts +217 -0
- package/dist/cli/backend/src/services/orc/orc-reply-route.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/orc/orc-reply-route.service.js +300 -0
- package/dist/cli/backend/src/services/orc/orc-reply-route.service.js.map +1 -0
- package/dist/cli/backend/src/services/system/auto-update.utils.d.ts +265 -0
- package/dist/cli/backend/src/services/system/auto-update.utils.d.ts.map +1 -0
- package/dist/cli/backend/src/services/system/auto-update.utils.js +397 -0
- package/dist/cli/backend/src/services/system/auto-update.utils.js.map +1 -0
- package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts +7 -1
- package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/task-pool/task-pool.service.js +14 -2
- package/dist/cli/backend/src/services/task-pool/task-pool.service.js.map +1 -1
- package/dist/cli/backend/src/services/v3/request-completion.d.ts +128 -0
- package/dist/cli/backend/src/services/v3/request-completion.d.ts.map +1 -0
- package/dist/cli/backend/src/services/v3/request-completion.js +311 -0
- package/dist/cli/backend/src/services/v3/request-completion.js.map +1 -0
- package/dist/cli/backend/src/services/v3/v3-data.service.d.ts +8 -0
- package/dist/cli/backend/src/services/v3/v3-data.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/v3/v3-data.service.js +11 -1
- package/dist/cli/backend/src/services/v3/v3-data.service.js.map +1 -1
- package/dist/cli/backend/src/types/settings.types.d.ts +8 -0
- package/dist/cli/backend/src/types/settings.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/settings.types.js +4 -0
- package/dist/cli/backend/src/types/settings.types.js.map +1 -1
- package/dist/cli/backend/src/utils/agent-reply-filter.utils.d.ts +75 -0
- package/dist/cli/backend/src/utils/agent-reply-filter.utils.d.ts.map +1 -0
- package/dist/cli/backend/src/utils/agent-reply-filter.utils.js +127 -0
- package/dist/cli/backend/src/utils/agent-reply-filter.utils.js.map +1 -0
- package/dist/cli/cli/src/commands/start.d.ts.map +1 -1
- package/dist/cli/cli/src/commands/start.js +14 -3
- package/dist/cli/cli/src/commands/start.js.map +1 -1
- package/dist/cli/cli/src/commands/update-status.d.ts +85 -0
- package/dist/cli/cli/src/commands/update-status.d.ts.map +1 -0
- package/dist/cli/cli/src/commands/update-status.js +162 -0
- package/dist/cli/cli/src/commands/update-status.js.map +1 -0
- package/dist/cli/cli/src/constants.d.ts +1 -1
- package/dist/cli/cli/src/constants.d.ts.map +1 -1
- package/dist/cli/cli/src/constants.js +1 -1
- package/dist/cli/cli/src/constants.js.map +1 -1
- package/dist/cli/cli/src/index.js +7 -0
- package/dist/cli/cli/src/index.js.map +1 -1
- package/dist/cli/cli/src/utils/backend-respawn.d.ts +50 -0
- package/dist/cli/cli/src/utils/backend-respawn.d.ts.map +1 -0
- package/dist/cli/cli/src/utils/backend-respawn.js +59 -0
- package/dist/cli/cli/src/utils/backend-respawn.js.map +1 -0
- package/dist/cli/config/constants.d.ts +56 -0
- package/dist/cli/config/constants.d.ts.map +1 -1
- package/dist/cli/config/constants.js +57 -0
- package/dist/cli/config/constants.js.map +1 -1
- package/dist/cli/config/index.d.ts +2 -2
- package/dist/cli/config/index.d.ts.map +1 -1
- package/dist/cli/config/index.js +2 -2
- package/dist/cli/config/index.js.map +1 -1
- package/frontend/dist/assets/{index-e05af897.js → index-71670b91.js} +22 -22
- package/frontend/dist/index.html +1 -1
- package/package.json +1 -1
- package/packages/crewly-agent/src/runtime/agent-runner.service.test.ts +39 -0
- package/packages/crewly-agent/src/runtime/agent-runner.service.ts +17 -17
- package/packages/crewly-agent/src/runtime/interactive-login-guard.test.ts +60 -0
- package/packages/crewly-agent/src/runtime/interactive-login-guard.ts +76 -0
- package/packages/crewly-agent/src/runtime/reply-tool-calls.test.ts +53 -0
- package/packages/crewly-agent/src/runtime/reply-tool-calls.ts +76 -0
- package/packages/crewly-agent/src/runtime/tool-registry.test.ts +8 -0
- package/packages/crewly-agent/src/runtime/tool-registry.ts +14 -0
package/frontend/dist/index.html
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<meta name="color-scheme" content="dark" />
|
|
8
8
|
<!-- Nunito font is self-hosted via @fontsource/nunito (imported in main.tsx) -->
|
|
9
9
|
<title>Crewly AI Studio</title>
|
|
10
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
+
<script type="module" crossorigin src="/assets/index-71670b91.js"></script>
|
|
11
11
|
<link rel="stylesheet" href="/assets/index-2bdb57b9.css">
|
|
12
12
|
</head>
|
|
13
13
|
<body class="bg-background-dark font-display text-text-primary-dark">
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crewly",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.145",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Multi-agent orchestration platform for AI coding teams — coordinates Claude Code, Gemini CLI, and Codex agents with a real-time web dashboard",
|
|
6
6
|
"workspaces": [
|
|
@@ -136,6 +136,45 @@ describe('AgentRunnerService', () => {
|
|
|
136
136
|
expect(runner.getHistoryLength()).toBe(1); // only user message
|
|
137
137
|
});
|
|
138
138
|
|
|
139
|
+
it('asks for a short user-facing reply (not a status report) when tools ran and no text came back', async () => {
|
|
140
|
+
mockGenerateText
|
|
141
|
+
.mockResolvedValueOnce({
|
|
142
|
+
text: '',
|
|
143
|
+
steps: [{ toolCalls: [{ toolCallId: 'tc-1', toolName: 'get_team_status', input: {} }], toolResults: [{ toolCallId: 'tc-1', output: {} }] }],
|
|
144
|
+
usage: { inputTokens: 10, outputTokens: 0 },
|
|
145
|
+
finishReason: 'stop',
|
|
146
|
+
})
|
|
147
|
+
.mockResolvedValueOnce({ text: '两个团队都在跑。', usage: { inputTokens: 5, outputTokens: 5 } });
|
|
148
|
+
|
|
149
|
+
const result = await runner.run('团队怎么样');
|
|
150
|
+
|
|
151
|
+
expect(result.text).toBe('两个团队都在跑。');
|
|
152
|
+
expect(mockGenerateText).toHaveBeenCalledTimes(2);
|
|
153
|
+
const fallbackCall = mockGenerateText.mock.calls[1][0] as { messages: Array<{ role: string; content: string }>; tools?: unknown };
|
|
154
|
+
expect(fallbackCall.tools).toBeUndefined();
|
|
155
|
+
// The history array is shared, so find the injected prompt rather than taking the last entry.
|
|
156
|
+
const prompt = fallbackCall.messages.find((m) => m.role === 'user' && /No status report/.test(String(m.content)));
|
|
157
|
+
expect(prompt).toBeDefined();
|
|
158
|
+
expect(fallbackCall.messages.some((m) => /summarize what you just did/i.test(String(m.content)))).toBe(false);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('asks for nothing more when the turn already replied with reply-chat (2026-09-26 status noise)', async () => {
|
|
162
|
+
mockGenerateText.mockResolvedValueOnce({
|
|
163
|
+
text: '',
|
|
164
|
+
steps: [{
|
|
165
|
+
toolCalls: [{ toolCallId: 'tc-1', toolName: 'bash_exec', input: { command: "bash config/skills/orchestrator/reply-chat/execute.sh '{\"content\":\"好了\"}'" } }],
|
|
166
|
+
toolResults: [{ toolCallId: 'tc-1', output: { success: true } }],
|
|
167
|
+
}],
|
|
168
|
+
usage: { inputTokens: 10, outputTokens: 0 },
|
|
169
|
+
finishReason: 'stop',
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const result = await runner.run('帮我重新登陆claude code');
|
|
173
|
+
|
|
174
|
+
expect(result.text).toBe('');
|
|
175
|
+
expect(mockGenerateText).toHaveBeenCalledTimes(1);
|
|
176
|
+
});
|
|
177
|
+
|
|
139
178
|
it('should track tool calls across steps', async () => {
|
|
140
179
|
mockGenerateText.mockResolvedValueOnce({
|
|
141
180
|
text: 'Done',
|
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
MODEL_CONTEXT_WINDOWS,
|
|
39
39
|
resolveMaxOutputTokens,
|
|
40
40
|
} from './types.js';
|
|
41
|
+
import { SUMMARY_FALLBACK_PROMPT, shouldRequestSummaryFallback } from './reply-tool-calls.js';
|
|
41
42
|
|
|
42
43
|
/**
|
|
43
44
|
* What a provider's finish reason means for the turn, and how to recover.
|
|
@@ -1572,9 +1573,11 @@ export class AgentRunnerService {
|
|
|
1572
1573
|
this.state.messages.push({ role: 'assistant', content: text });
|
|
1573
1574
|
}
|
|
1574
1575
|
|
|
1575
|
-
// Empty response fallback: if model made tool calls but produced no text
|
|
1576
|
-
// prompt it once more
|
|
1577
|
-
|
|
1576
|
+
// Empty response fallback: if model made tool calls but produced no text,
|
|
1577
|
+
// prompt it once more for a short reply (prevents silent completions).
|
|
1578
|
+
// Skipped when a reply tool already sent the answer: the "summary" that
|
|
1579
|
+
// followed was the status report the owner got as noise (2026-09-26).
|
|
1580
|
+
if (shouldRequestSummaryFallback(text, toolCalls)) {
|
|
1578
1581
|
console.warn('[AgentRunner] Empty text response after tool calls, requesting summary fallback');
|
|
1579
1582
|
const fallbackResult = await this.requestSummaryFallback();
|
|
1580
1583
|
if (fallbackResult) {
|
|
@@ -1760,9 +1763,9 @@ export class AgentRunnerService {
|
|
|
1760
1763
|
this.state.messages.push({ role: 'assistant', content: finalText });
|
|
1761
1764
|
}
|
|
1762
1765
|
|
|
1763
|
-
// Empty response fallback
|
|
1764
|
-
//
|
|
1765
|
-
if (
|
|
1766
|
+
// Empty response fallback (see the streaming path): a short reply, and
|
|
1767
|
+
// none at all when a reply tool already sent the answer.
|
|
1768
|
+
if (shouldRequestSummaryFallback(finalText, toolCalls)) {
|
|
1766
1769
|
console.warn('[AgentRunner] Empty text response after tool calls, requesting summary fallback');
|
|
1767
1770
|
const fallbackResult = await this.requestSummaryFallback();
|
|
1768
1771
|
if (fallbackResult) {
|
|
@@ -1991,24 +1994,21 @@ export class AgentRunnerService {
|
|
|
1991
1994
|
}
|
|
1992
1995
|
|
|
1993
1996
|
/**
|
|
1994
|
-
*
|
|
1995
|
-
* but no text output. Injects
|
|
1996
|
-
*
|
|
1997
|
+
* Ask the model for a closing reply when the previous response had tool
|
|
1998
|
+
* calls but no text output. Injects {@link SUMMARY_FALLBACK_PROMPT} (a short
|
|
1999
|
+
* reply to the person, in their language — not a status report) and makes a
|
|
2000
|
+
* single generateText call with no tools to force a text-only response.
|
|
1997
2001
|
*
|
|
1998
|
-
* @returns The
|
|
2002
|
+
* @returns The reply text, or empty string if the fallback also fails
|
|
1999
2003
|
*/
|
|
2000
2004
|
private async requestSummaryFallback(): Promise<string> {
|
|
2001
2005
|
if (!this.model) return '';
|
|
2002
2006
|
|
|
2003
|
-
|
|
2004
|
-
'请用文字总结你刚才完成的工作和发现的结果,然后调用report-status汇报。' +
|
|
2005
|
-
'Please summarize what you just did, what you found, and any issues encountered. ' +
|
|
2006
|
-
'Then call report-status to report your status.';
|
|
2007
|
-
|
|
2008
|
-
this.state.messages.push({ role: 'user', content: prompt });
|
|
2007
|
+
this.state.messages.push({ role: 'user', content: SUMMARY_FALLBACK_PROMPT });
|
|
2009
2008
|
|
|
2010
2009
|
try {
|
|
2011
|
-
const
|
|
2010
|
+
const generateFn = this._generateTextFn || (generateText as Function);
|
|
2011
|
+
const fallback = await generateFn({
|
|
2012
2012
|
model: this.model,
|
|
2013
2013
|
system: this.state.systemPrompt,
|
|
2014
2014
|
messages: this.state.messages,
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the interactive harness login guard of bash_exec.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import { HARNESS_LOGIN_SKILL_PATH, checkInteractiveLoginCommand, detectInteractiveLogin } from './interactive-login-guard.js';
|
|
7
|
+
|
|
8
|
+
describe('detectInteractiveLogin', () => {
|
|
9
|
+
it.each([
|
|
10
|
+
['claude setup-token', 'claude setup-token'],
|
|
11
|
+
['~/.local/bin/claude setup-token', 'claude setup-token'],
|
|
12
|
+
['cd ~ && claude setup-token 2>&1 | tee /tmp/x', 'claude setup-token'],
|
|
13
|
+
['script -q /dev/null claude setup-token', 'claude setup-token'],
|
|
14
|
+
["tmux send-keys -t login 'claude setup-token' Enter", 'claude setup-token'],
|
|
15
|
+
['claude /login', 'claude /login'],
|
|
16
|
+
['claude "/login"', 'claude /login'],
|
|
17
|
+
['echo /login | claude', 'claude /login'],
|
|
18
|
+
["printf '/login\\n' | ~/.local/bin/claude", 'claude /login'],
|
|
19
|
+
['claude auth login', 'claude auth login'],
|
|
20
|
+
['claude auth login --email someone@example.com', 'claude auth login'],
|
|
21
|
+
['claude login', 'claude auth login'],
|
|
22
|
+
['codex login', 'codex login'],
|
|
23
|
+
['codex login --device-auth', 'codex login'],
|
|
24
|
+
['nohup codex login --device-auth > /tmp/l.log 2>&1 &', 'codex login'],
|
|
25
|
+
['agy login', 'agy login'],
|
|
26
|
+
['antigravity auth login', 'agy login'],
|
|
27
|
+
])('%s', (command, label) => {
|
|
28
|
+
expect(detectInteractiveLogin(command)).toBe(label);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it.each([
|
|
32
|
+
'claude auth status',
|
|
33
|
+
'claude --version',
|
|
34
|
+
'claude -p "summarise the repo"',
|
|
35
|
+
'codex login status',
|
|
36
|
+
'codex exec "fix the test"',
|
|
37
|
+
'cat backend/src/services/harness/claude-config.utils.ts',
|
|
38
|
+
'grep -rn setup-token backend/src/services/harness/login-rules.ts',
|
|
39
|
+
'bash config/skills/orchestrator/harness-login/execute.sh --harness claude',
|
|
40
|
+
'ls ~/.claude',
|
|
41
|
+
'echo login',
|
|
42
|
+
'',
|
|
43
|
+
])('allows %s', (command) => {
|
|
44
|
+
expect(detectInteractiveLogin(command)).toBeNull();
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('checkInteractiveLoginCommand', () => {
|
|
49
|
+
it('explains why and names the skill', () => {
|
|
50
|
+
const message = checkInteractiveLoginCommand('claude setup-token');
|
|
51
|
+
expect(message).toContain('`claude setup-token` is an interactive login');
|
|
52
|
+
expect(message).toContain('stale');
|
|
53
|
+
expect(message).toContain(HARNESS_LOGIN_SKILL_PATH);
|
|
54
|
+
expect(message).toContain('Do not retry');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('returns null for ordinary commands', () => {
|
|
58
|
+
expect(checkInteractiveLoginCommand('npm run build')).toBeNull();
|
|
59
|
+
});
|
|
60
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive harness login guard for the `bash_exec` tool.
|
|
3
|
+
*
|
|
4
|
+
* `claude setup-token`, `claude /login`, `claude auth login`, `codex login`
|
|
5
|
+
* and an Antigravity (`agy`) login print a link and then wait for the user
|
|
6
|
+
* to paste a code into the same process. `bash_exec` is one-shot: the
|
|
7
|
+
* process is killed when the tool call returns (or times out), so a code the
|
|
8
|
+
* owner pastes back later can never be redeemed.
|
|
9
|
+
*
|
|
10
|
+
* Incident 2026-09-26: the orchestrator ran `claude setup-token` this way,
|
|
11
|
+
* sent the owner the link, and every authorization code the owner pasted
|
|
12
|
+
* back went stale. Crewly's login broker keeps the login alive in its own
|
|
13
|
+
* terminal; the orchestrator reaches it with the `harness-login` skill.
|
|
14
|
+
*
|
|
15
|
+
* Status checks (`claude auth status`, `codex login status`) stay allowed.
|
|
16
|
+
*
|
|
17
|
+
* @module runtime/interactive-login-guard
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** Where a command word may start: line start, a separator, a quote, or a path slash. */
|
|
21
|
+
const CMD_START = String.raw`(?:^|[\s;&|()'"\x60/])`;
|
|
22
|
+
|
|
23
|
+
/** Flags without values between the command and its subcommand (`codex --verbose login`). */
|
|
24
|
+
const FLAGS = String.raw`(?:-{1,2}[\w-]+\s+)*`;
|
|
25
|
+
|
|
26
|
+
/** Interactive login commands, each with a label for the refusal message. */
|
|
27
|
+
const INTERACTIVE_LOGIN_PATTERNS: ReadonlyArray<{ pattern: RegExp; label: string }> = [
|
|
28
|
+
{ pattern: new RegExp(`${CMD_START}claude\\s[^\\n;&|]*?\\bsetup-token\\b`, 'i'), label: 'claude setup-token' },
|
|
29
|
+
{ pattern: new RegExp(`${CMD_START}claude(?=\\s)[^\\n;&|]*?[\\s'"]/login\\b`, 'i'), label: 'claude /login' },
|
|
30
|
+
{ pattern: new RegExp(`${CMD_START}claude\\s+${FLAGS}(?:auth\\s+)?login\\b`, 'i'), label: 'claude auth login' },
|
|
31
|
+
// `/login` typed into claude through a pipe: `echo /login | claude`.
|
|
32
|
+
{ pattern: new RegExp(`/login\\b[^\\n;&]*\\|\\s*(?:\\S*/)?claude\\b`, 'i'), label: 'claude /login' },
|
|
33
|
+
{ pattern: new RegExp(`${CMD_START}codex\\s+${FLAGS}login\\b(?!\\s+status\\b)`, 'i'), label: 'codex login' },
|
|
34
|
+
{ pattern: new RegExp(`${CMD_START}(?:agy|antigravity)\\s+${FLAGS}(?:auth\\s+)?login\\b`, 'i'), label: 'agy login' },
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
/** Where the orchestrator's login skill lives (relative to the Crewly project root). */
|
|
38
|
+
export const HARNESS_LOGIN_SKILL_PATH = 'config/skills/orchestrator/harness-login/execute.sh';
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Which interactive harness login a shell command starts, if any.
|
|
42
|
+
*
|
|
43
|
+
* @param command - Raw shell command
|
|
44
|
+
* @returns A label such as "claude setup-token", or null when the command is not one
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* detectInteractiveLogin('claude setup-token'); // 'claude setup-token'
|
|
49
|
+
* detectInteractiveLogin('codex login status'); // null
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export function detectInteractiveLogin(command: string): string | null {
|
|
53
|
+
if (typeof command !== 'string' || !command) return null;
|
|
54
|
+
for (const { pattern, label } of INTERACTIVE_LOGIN_PATTERNS) {
|
|
55
|
+
if (pattern.test(command)) return label;
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The refusal `bash_exec` returns for an interactive harness login, or null
|
|
62
|
+
* when the command may run.
|
|
63
|
+
*
|
|
64
|
+
* @param command - Raw shell command
|
|
65
|
+
* @returns Refusal message pointing at the `harness-login` skill, or null
|
|
66
|
+
*/
|
|
67
|
+
export function checkInteractiveLoginCommand(command: string): string | null {
|
|
68
|
+
const label = detectInteractiveLogin(command);
|
|
69
|
+
if (!label) return null;
|
|
70
|
+
return (
|
|
71
|
+
`Refused: \`${label}\` is an interactive login. It would be killed when this tool call returns, so the code the owner pastes back would be stale. ` +
|
|
72
|
+
`Use the harness-login skill instead: bash ${HARNESS_LOGIN_SKILL_PATH} --harness claude|codex [--switch-account] ` +
|
|
73
|
+
'(orchestrator only — any other agent asks the orchestrator). Crewly keeps the login alive, sends the owner the link and types in the code they paste. ' +
|
|
74
|
+
'Do not retry this command or a variation.'
|
|
75
|
+
);
|
|
76
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for reply tool detection and the empty-text summary fallback rule.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import { SUMMARY_FALLBACK_PROMPT, isReplyToolCall, shouldRequestSummaryFallback } from './reply-tool-calls.js';
|
|
7
|
+
|
|
8
|
+
const REPLY_CHAT = {
|
|
9
|
+
toolName: 'bash_exec',
|
|
10
|
+
args: { command: `bash config/skills/orchestrator/reply-chat/execute.sh '{"conversationId":"a721f48d","content":"链接在下面"}'` },
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
describe('isReplyToolCall', () => {
|
|
14
|
+
it('recognises reply tools and reply skills run through the shell', () => {
|
|
15
|
+
expect(isReplyToolCall({ toolName: 'reply_slack' })).toBe(true);
|
|
16
|
+
expect(isReplyToolCall({ toolName: 'reply-slack' })).toBe(true);
|
|
17
|
+
expect(isReplyToolCall(REPLY_CHAT)).toBe(true);
|
|
18
|
+
expect(isReplyToolCall({ toolName: 'bash_exec', args: { command: 'bash config/skills/agent/core/reply-channel/execute.sh --text hi' } })).toBe(true);
|
|
19
|
+
expect(isReplyToolCall({ toolName: 'Bash', args: { command: 'bash config/skills/orchestrator/reply-slack/execute.sh x' } })).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('ignores other tools and other shell commands', () => {
|
|
23
|
+
expect(isReplyToolCall({ toolName: 'get_team_status' })).toBe(false);
|
|
24
|
+
expect(isReplyToolCall({ toolName: 'bash_exec', args: { command: 'claude auth status' } })).toBe(false);
|
|
25
|
+
expect(isReplyToolCall({ toolName: 'bash_exec', args: {} })).toBe(false);
|
|
26
|
+
expect(isReplyToolCall({ toolName: 'read_file', args: { command: 'reply-chat' } })).toBe(false);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('shouldRequestSummaryFallback', () => {
|
|
31
|
+
it('asks for a closing reply when tools ran and nothing was said', () => {
|
|
32
|
+
expect(shouldRequestSummaryFallback('', [{ toolName: 'bash_exec', args: { command: 'claude --version' } }])).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('stays silent when the reply already went out with a reply tool (2026-09-26 noise)', () => {
|
|
36
|
+
expect(shouldRequestSummaryFallback('', [{ toolName: 'bash_exec', args: { command: 'claude --version' } }, REPLY_CHAT])).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('never runs when there is text or no tool call', () => {
|
|
40
|
+
expect(shouldRequestSummaryFallback('Done.', [REPLY_CHAT])).toBe(false);
|
|
41
|
+
expect(shouldRequestSummaryFallback('', [])).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('SUMMARY_FALLBACK_PROMPT', () => {
|
|
46
|
+
it('asks for a short user-facing reply in their language, not a status report', () => {
|
|
47
|
+
expect(SUMMARY_FALLBACK_PROMPT).toMatch(/in their language/);
|
|
48
|
+
expect(SUMMARY_FALLBACK_PROMPT).toMatch(/No status report/);
|
|
49
|
+
expect(SUMMARY_FALLBACK_PROMPT).toMatch(/do not say you sent anything unless a tool actually sent it/);
|
|
50
|
+
expect(SUMMARY_FALLBACK_PROMPT).not.toMatch(/summarize what you just did/i);
|
|
51
|
+
expect(SUMMARY_FALLBACK_PROMPT).not.toMatch(/report-status/);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reply tool calls and the empty-text summary fallback.
|
|
3
|
+
*
|
|
4
|
+
* When a turn ends with tool calls but no text, the runner used to ask the
|
|
5
|
+
* model to "summarize what you just did, what you found, and any issues".
|
|
6
|
+
* For an agent answering a person, that summary is what reaches them: the
|
|
7
|
+
* owner's Slack DM got several English meta reports in a row — "I've sent the
|
|
8
|
+
* reply. Here's my status: *What the user asked* … *What I did* … *Next step*
|
|
9
|
+
* … *Note on the ticket instruction*" (2026-09-26). The status-report shape
|
|
10
|
+
* then stayed in the conversation history and the model kept repeating it.
|
|
11
|
+
*
|
|
12
|
+
* Two changes live here:
|
|
13
|
+
* - {@link shouldRequestSummaryFallback}: no fallback when the turn already
|
|
14
|
+
* sent its reply with a reply tool — the answer is out; anything more is
|
|
15
|
+
* noise.
|
|
16
|
+
* - {@link SUMMARY_FALLBACK_PROMPT}: the fallback asks for a short reply to
|
|
17
|
+
* the person, in their language, not a status report.
|
|
18
|
+
*
|
|
19
|
+
* @module runtime/reply-tool-calls
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** The slice of a tool call record these helpers read. */
|
|
23
|
+
export interface ToolCallLike {
|
|
24
|
+
toolName: string;
|
|
25
|
+
args?: Record<string, unknown>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Tools that post a reply to a person by themselves. */
|
|
29
|
+
const REPLY_TOOL_NAMES: ReadonlySet<string> = new Set(['reply_slack', 'reply-slack', 'reply_chat', 'reply-chat', 'send_chat_response']);
|
|
30
|
+
|
|
31
|
+
/** Shell tools whose command can run a reply skill. */
|
|
32
|
+
const SHELL_TOOL_NAMES: ReadonlySet<string> = new Set(['bash_exec', 'bash', 'Bash', 'shell']);
|
|
33
|
+
|
|
34
|
+
/** Reply skills run through the shell (`bash config/skills/…/reply-chat/execute.sh …`). */
|
|
35
|
+
const REPLY_SKILL_PATTERN = /\b(?:reply-chat|reply-slack|reply-gchat|reply-remote|reply-channel|send-chat-response)\b/;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Whether a tool call sent a reply to a person.
|
|
39
|
+
*
|
|
40
|
+
* @param call - Tool call record
|
|
41
|
+
* @returns True for a reply tool, or a shell call that runs a reply skill
|
|
42
|
+
*/
|
|
43
|
+
export function isReplyToolCall(call: ToolCallLike): boolean {
|
|
44
|
+
if (REPLY_TOOL_NAMES.has(call.toolName)) return true;
|
|
45
|
+
if (!SHELL_TOOL_NAMES.has(call.toolName)) return false;
|
|
46
|
+
const command = call.args?.command;
|
|
47
|
+
return typeof command === 'string' && REPLY_SKILL_PATTERN.test(command);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Whether the runner should ask the model for a closing text.
|
|
52
|
+
*
|
|
53
|
+
* Only when the turn produced no text, made tool calls, and none of them
|
|
54
|
+
* sent the reply already.
|
|
55
|
+
*
|
|
56
|
+
* @param text - The turn's final text
|
|
57
|
+
* @param toolCalls - The turn's tool calls
|
|
58
|
+
* @returns True when the fallback should run
|
|
59
|
+
*/
|
|
60
|
+
export function shouldRequestSummaryFallback(text: string, toolCalls: readonly ToolCallLike[]): boolean {
|
|
61
|
+
if (text || toolCalls.length === 0) return false;
|
|
62
|
+
return !toolCalls.some(isReplyToolCall);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Follow-up prompt when a turn ended with tool calls and no text: a short
|
|
67
|
+
* answer to whoever wrote, in their language — never a status report, never
|
|
68
|
+
* a claim of having sent something.
|
|
69
|
+
*/
|
|
70
|
+
export const SUMMARY_FALLBACK_PROMPT =
|
|
71
|
+
'请直接回复给你发消息的人:用对方的语言,简短(1-3 句),只写答案或结果本身。' +
|
|
72
|
+
'不要写状态汇报(不要"用户问了什么 / 我做了什么 / 下一步"之类的小标题),不要描述你调用了哪些工具,' +
|
|
73
|
+
'也不要说你已经发送了什么——除非这一轮真的用工具发出去了。\n' +
|
|
74
|
+
'Reply directly to the person who wrote to you: in their language, short (1-3 sentences), only the answer or result itself. ' +
|
|
75
|
+
'No status report (no "What the user asked / What I did / Next step" sections), do not describe your tool calls, ' +
|
|
76
|
+
'and do not say you sent anything unless a tool actually sent it in this turn.';
|
|
@@ -2245,6 +2245,14 @@ describe('Tool Registry', () => {
|
|
|
2245
2245
|
expect(result.error).toContain('blocked');
|
|
2246
2246
|
});
|
|
2247
2247
|
|
|
2248
|
+
it('refuses interactive harness logins and points at harness-login (2026-09-26)', async () => {
|
|
2249
|
+
const result = await bashTools.bash_exec.execute({ command: 'claude setup-token' }) as any;
|
|
2250
|
+
expect(result.success).toBe(false);
|
|
2251
|
+
expect(result.exitCode).toBe(126);
|
|
2252
|
+
expect(result.error).toContain('harness-login');
|
|
2253
|
+
expect(result.stdout).toBe('');
|
|
2254
|
+
});
|
|
2255
|
+
|
|
2248
2256
|
it('should handle command failure gracefully', async () => {
|
|
2249
2257
|
const result = await bashTools.bash_exec.execute({ command: 'false' }) as any;
|
|
2250
2258
|
expect(result.success).toBe(false);
|
|
@@ -21,6 +21,7 @@ import { OutputFilterService } from './output-filter.service.js';
|
|
|
21
21
|
import { createWebSearchTool } from './web-search.tool.js';
|
|
22
22
|
import { createComputerTool } from './computer.tool.js';
|
|
23
23
|
import { createDesktopTaskTool } from './desktop-task.tool.js';
|
|
24
|
+
import { checkInteractiveLoginCommand } from './interactive-login-guard.js';
|
|
24
25
|
|
|
25
26
|
/** TTL for delegation idle event subscriptions (minutes) */
|
|
26
27
|
const DELEGATION_SUBSCRIPTION_TTL_MINUTES = 120;
|
|
@@ -1821,6 +1822,19 @@ export function createTools(client: CrewlyApiClient, sessionName: string, projec
|
|
|
1821
1822
|
execute: async ({ command, cwd, timeout }) => {
|
|
1822
1823
|
const cmd = command as string;
|
|
1823
1824
|
|
|
1825
|
+
// Interactive harness logins die with this one-shot call; the owner's
|
|
1826
|
+
// pasted code would go stale (2026-09-26). Point at harness-login.
|
|
1827
|
+
const loginRefusal = checkInteractiveLoginCommand(cmd);
|
|
1828
|
+
if (loginRefusal) {
|
|
1829
|
+
return {
|
|
1830
|
+
success: false,
|
|
1831
|
+
exitCode: 126,
|
|
1832
|
+
stdout: '',
|
|
1833
|
+
stderr: loginRefusal,
|
|
1834
|
+
error: loginRefusal,
|
|
1835
|
+
};
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1824
1838
|
// Check command against blocklist before execution
|
|
1825
1839
|
const blockReason = validateBashCommand(cmd);
|
|
1826
1840
|
if (blockReason) {
|