fix: batch post-483 workspace follow-ups (#508)
Co-authored-by: Aurora release bot <release@outsourc-e.com>
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
# Or pull pre-built:
|
||||
# docker pull ghcr.io/outsourc-e/hermes-workspace:latest
|
||||
#
|
||||
FROM tianon/gosu:1.19-bookworm AS gosu_source
|
||||
FROM tianon/gosu:1.17-bookworm AS gosu_source
|
||||
# ─── build stage ─────────────────────────────────────────────────────────
|
||||
FROM node:22-slim AS build
|
||||
RUN corepack enable && apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { buildWorkspaceScopedTextMessage } from './workspace-message-scope'
|
||||
import {
|
||||
buildWorkspaceScopedTextMessage,
|
||||
stripWorkspaceDirective,
|
||||
} from './workspace-message-scope'
|
||||
|
||||
describe('buildWorkspaceScopedTextMessage', () => {
|
||||
it('prepends an explicit active workspace directive to plain text chat messages', () => {
|
||||
@@ -39,4 +42,12 @@ describe('buildWorkspaceScopedTextMessage', () => {
|
||||
}),
|
||||
).toBe('hello')
|
||||
})
|
||||
|
||||
it('strips the workspace directive back out for user-visible rendering', () => {
|
||||
expect(
|
||||
stripWorkspaceDirective(
|
||||
'<workspace_context active="true" name="Home" path="/Users/aurora/workspace" />\n\nRun the tests',
|
||||
),
|
||||
).toBe('Run the tests')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -4,6 +4,9 @@ export type WorkspaceScope = {
|
||||
isValid?: boolean
|
||||
}
|
||||
|
||||
const WORKSPACE_DIRECTIVE_RE =
|
||||
/^\s*<workspace_context\s+active="true"\s+name="[^"]*"\s+path="[^"]*"\s*\/?>\s*/i
|
||||
|
||||
function escapeAttribute(value: string): string {
|
||||
return value
|
||||
.replace(/&/g, '&')
|
||||
@@ -28,3 +31,8 @@ export function buildWorkspaceScopedTextMessage(
|
||||
if (!directive) return message
|
||||
return `${directive}\n\n${message}`
|
||||
}
|
||||
|
||||
export function stripWorkspaceDirective(message: string): string {
|
||||
if (!message.includes('<workspace_context active="true"')) return message
|
||||
return message.replace(WORKSPACE_DIRECTIVE_RE, '').trimStart()
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ import {
|
||||
isRecentSession,
|
||||
resetPendingSend,
|
||||
setPendingGeneration,
|
||||
clearPendingSendForSession,
|
||||
} from './pending-send'
|
||||
import { useChatMeasurements } from './hooks/use-chat-measurements'
|
||||
import { useChatHistory } from './hooks/use-chat-history'
|
||||
@@ -100,7 +101,7 @@ import { MobileSessionsPanel } from '@/components/mobile-sessions-panel'
|
||||
import { ContextAlertModal } from '@/components/usage-meter/context-alert-modal'
|
||||
import { ErrorToastContainer, showErrorToast } from '@/components/error-toast'
|
||||
// ContextMeter removed — ContextBar (PR #32) replaces it
|
||||
import { useChatStore } from '@/stores/chat-store'
|
||||
import { useChatStore, persistRecoveryMessage } from '@/stores/chat-store'
|
||||
import { useResearchCard } from '@/hooks/use-research-card'
|
||||
// MOBILE_TAB_BAR_OFFSET removed — tab bar always hidden in chat
|
||||
import { useTapDebug } from '@/hooks/use-tap-debug'
|
||||
@@ -1128,7 +1129,7 @@ export function ChatScreen({
|
||||
},
|
||||
[queryClient],
|
||||
),
|
||||
onComplete: useCallback(() => {
|
||||
onComplete: useCallback((message: ChatMessage) => {
|
||||
const activeSend = activeSendRef.current
|
||||
if (activeSend?.clientId) {
|
||||
updateHistoryMessageByClientIdEverywhere(
|
||||
@@ -1140,6 +1141,13 @@ export function ChatScreen({
|
||||
}),
|
||||
)
|
||||
}
|
||||
if (activeSend?.sessionKey) {
|
||||
persistRecoveryMessage(activeSend.sessionKey, message)
|
||||
clearPendingSendForSession(
|
||||
activeSend.sessionKey,
|
||||
activeSend.friendlyId,
|
||||
)
|
||||
}
|
||||
activeSendRef.current = null
|
||||
refreshHistoryRef.current()
|
||||
setSending(false)
|
||||
|
||||
41
src/screens/chat/utils.test.ts
Normal file
41
src/screens/chat/utils.test.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { normalizeSessions, textFromMessage } from './utils'
|
||||
import type { ChatMessage, SessionSummary } from './types'
|
||||
|
||||
describe('chat utils workspace directive cleanup', () => {
|
||||
it('hides workspace_context directives from user-visible message text', () => {
|
||||
const message: ChatMessage = {
|
||||
role: 'user',
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: '<workspace_context active="true" name="Home" path="/Users/aurora/workspace" />\n\nRun the tests',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
expect(textFromMessage(message)).toBe('Run the tests')
|
||||
})
|
||||
|
||||
it('strips workspace_context directives from session previews and derived titles', () => {
|
||||
const sessions = normalizeSessions([
|
||||
{
|
||||
key: 'session-1',
|
||||
friendlyId: 'session-1',
|
||||
preview:
|
||||
'<workspace_context active="true" name="Home" path="/Users/aurora/workspace" />\n\nReview the open PRs',
|
||||
},
|
||||
{
|
||||
key: 'session-2',
|
||||
friendlyId: 'session-2',
|
||||
derivedTitle:
|
||||
'<workspace_context active="true" name="Home" path="/Users/aurora/workspace" />\n\nFix Docker publish',
|
||||
},
|
||||
] satisfies Array<SessionSummary>)
|
||||
|
||||
expect(sessions[0]?.preview).toBe('Review the open PRs')
|
||||
expect(sessions[0]?.derivedTitle).toBe('Review the open PRs')
|
||||
expect(sessions[1]?.derivedTitle).toBe('Fix Docker publish')
|
||||
})
|
||||
})
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
SessionTitleStatus,
|
||||
ToolCallContent,
|
||||
} from './types'
|
||||
import { stripWorkspaceDirective } from '../../lib/workspace-message-scope'
|
||||
|
||||
export function deriveFriendlyIdFromKey(key: string | undefined): string {
|
||||
if (!key) return 'main'
|
||||
@@ -52,7 +53,7 @@ function stripChannelPrefix(text: string): string {
|
||||
* and [Telegram/Signal/etc ...] headers, leaving just the user's text.
|
||||
*/
|
||||
function cleanUserText(raw: string): string {
|
||||
let text = raw
|
||||
let text = stripWorkspaceDirective(raw)
|
||||
|
||||
// Remove "Conversation info (untrusted metadata):" headers + JSON block
|
||||
// Format: "Conversation info (untrusted metadata):\n```json\n{...}\n```\n\n"
|
||||
@@ -226,15 +227,15 @@ export function normalizeSessions(
|
||||
: undefined
|
||||
const explicitTitle =
|
||||
typeof session.title === 'string' && session.title.trim().length > 0
|
||||
? session.title.trim()
|
||||
? cleanUserText(session.title.trim()) || session.title.trim()
|
||||
: undefined
|
||||
const derivedTitle =
|
||||
typeof session.derivedTitle === 'string' &&
|
||||
session.derivedTitle.trim().length > 0
|
||||
? session.derivedTitle.trim()
|
||||
? cleanUserText(session.derivedTitle.trim()) || session.derivedTitle.trim()
|
||||
: typeof session.preview === 'string' &&
|
||||
session.preview.trim().length > 0
|
||||
? session.preview.trim()
|
||||
? cleanUserText(session.preview.trim()) || session.preview.trim()
|
||||
: undefined
|
||||
const titleStatus = deriveTitleStatus(
|
||||
label,
|
||||
@@ -261,7 +262,10 @@ export function normalizeSessions(
|
||||
titleStatus,
|
||||
titleSource,
|
||||
titleError: session.titleError ?? null,
|
||||
preview: session.preview ?? null,
|
||||
preview:
|
||||
typeof session.preview === 'string'
|
||||
? cleanUserText(session.preview) || session.preview.trim() || null
|
||||
: session.preview ?? null,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -478,7 +478,10 @@ const config = defineConfig(({ mode, command }) => {
|
||||
// 2. $PORT env var (for containers, reverse proxies, WhatsApp bridge collisions, etc. — see #96)
|
||||
// 3. default 3000 (matches README/docs/docker-compose expectations)
|
||||
port: process.env.PORT ? Number(process.env.PORT) : 3000,
|
||||
strictPort: false, // allow fallback if port is taken, but log clearly
|
||||
// Managed Workspace launchers expect a stable port. Fail loudly instead
|
||||
// of silently hopping to 3001+ so launchctl/service health matches the
|
||||
// actual listening socket.
|
||||
strictPort: true,
|
||||
allowedHosts: true,
|
||||
watch: {
|
||||
ignored: [
|
||||
|
||||
Reference in New Issue
Block a user