Skip to content

Commit b69aa34

Browse files
outsourc-eAurora release bot
andauthored
fix: batch post-483 workspace follow-ups (#508)
Co-authored-by: Aurora release bot <release@outsourc-e.com>
1 parent fbd7877 commit b69aa34

7 files changed

Lines changed: 85 additions & 10 deletions

File tree

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
# Or pull pre-built:
1010
# docker pull ghcr.io/outsourc-e/hermes-workspace:latest
1111
#
12-
FROM tianon/gosu:1.19-bookworm AS gosu_source
12+
FROM tianon/gosu:1.17-bookworm AS gosu_source
1313
# ─── build stage ─────────────────────────────────────────────────────────
1414
FROM node:22-slim AS build
1515
RUN corepack enable && apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/*

src/lib/workspace-message-scope.test.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { describe, expect, it } from 'vitest'
22

3-
import { buildWorkspaceScopedTextMessage } from './workspace-message-scope'
3+
import {
4+
buildWorkspaceScopedTextMessage,
5+
stripWorkspaceDirective,
6+
} from './workspace-message-scope'
47

58
describe('buildWorkspaceScopedTextMessage', () => {
69
it('prepends an explicit active workspace directive to plain text chat messages', () => {
@@ -39,4 +42,12 @@ describe('buildWorkspaceScopedTextMessage', () => {
3942
}),
4043
).toBe('hello')
4144
})
45+
46+
it('strips the workspace directive back out for user-visible rendering', () => {
47+
expect(
48+
stripWorkspaceDirective(
49+
'<workspace_context active="true" name="Home" path="/Users/aurora/workspace" />\n\nRun the tests',
50+
),
51+
).toBe('Run the tests')
52+
})
4253
})

src/lib/workspace-message-scope.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ export type WorkspaceScope = {
44
isValid?: boolean
55
}
66

7+
const WORKSPACE_DIRECTIVE_RE =
8+
/^\s*<workspace_context\s+active="true"\s+name="[^"]*"\s+path="[^"]*"\s*\/?>\s*/i
9+
710
function escapeAttribute(value: string): string {
811
return value
912
.replace(/&/g, '&amp;')
@@ -28,3 +31,8 @@ export function buildWorkspaceScopedTextMessage(
2831
if (!directive) return message
2932
return `${directive}\n\n${message}`
3033
}
34+
35+
export function stripWorkspaceDirective(message: string): string {
36+
if (!message.includes('<workspace_context active="true"')) return message
37+
return message.replace(WORKSPACE_DIRECTIVE_RE, '').trimStart()
38+
}

src/screens/chat/chat-screen.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import {
4949
isRecentSession,
5050
resetPendingSend,
5151
setPendingGeneration,
52+
clearPendingSendForSession,
5253
} from './pending-send'
5354
import { useChatMeasurements } from './hooks/use-chat-measurements'
5455
import { useChatHistory } from './hooks/use-chat-history'
@@ -100,7 +101,7 @@ import { MobileSessionsPanel } from '@/components/mobile-sessions-panel'
100101
import { ContextAlertModal } from '@/components/usage-meter/context-alert-modal'
101102
import { ErrorToastContainer, showErrorToast } from '@/components/error-toast'
102103
// ContextMeter removed — ContextBar (PR #32) replaces it
103-
import { useChatStore } from '@/stores/chat-store'
104+
import { useChatStore, persistRecoveryMessage } from '@/stores/chat-store'
104105
import { useResearchCard } from '@/hooks/use-research-card'
105106
// MOBILE_TAB_BAR_OFFSET removed — tab bar always hidden in chat
106107
import { useTapDebug } from '@/hooks/use-tap-debug'
@@ -1128,7 +1129,7 @@ export function ChatScreen({
11281129
},
11291130
[queryClient],
11301131
),
1131-
onComplete: useCallback(() => {
1132+
onComplete: useCallback((message: ChatMessage) => {
11321133
const activeSend = activeSendRef.current
11331134
if (activeSend?.clientId) {
11341135
updateHistoryMessageByClientIdEverywhere(
@@ -1140,6 +1141,13 @@ export function ChatScreen({
11401141
}),
11411142
)
11421143
}
1144+
if (activeSend?.sessionKey) {
1145+
persistRecoveryMessage(activeSend.sessionKey, message)
1146+
clearPendingSendForSession(
1147+
activeSend.sessionKey,
1148+
activeSend.friendlyId,
1149+
)
1150+
}
11431151
activeSendRef.current = null
11441152
refreshHistoryRef.current()
11451153
setSending(false)

src/screens/chat/utils.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { describe, expect, it } from 'vitest'
2+
3+
import { normalizeSessions, textFromMessage } from './utils'
4+
import type { ChatMessage, SessionSummary } from './types'
5+
6+
describe('chat utils workspace directive cleanup', () => {
7+
it('hides workspace_context directives from user-visible message text', () => {
8+
const message: ChatMessage = {
9+
role: 'user',
10+
content: [
11+
{
12+
type: 'text',
13+
text: '<workspace_context active="true" name="Home" path="/Users/aurora/workspace" />\n\nRun the tests',
14+
},
15+
],
16+
}
17+
18+
expect(textFromMessage(message)).toBe('Run the tests')
19+
})
20+
21+
it('strips workspace_context directives from session previews and derived titles', () => {
22+
const sessions = normalizeSessions([
23+
{
24+
key: 'session-1',
25+
friendlyId: 'session-1',
26+
preview:
27+
'<workspace_context active="true" name="Home" path="/Users/aurora/workspace" />\n\nReview the open PRs',
28+
},
29+
{
30+
key: 'session-2',
31+
friendlyId: 'session-2',
32+
derivedTitle:
33+
'<workspace_context active="true" name="Home" path="/Users/aurora/workspace" />\n\nFix Docker publish',
34+
},
35+
] satisfies Array<SessionSummary>)
36+
37+
expect(sessions[0]?.preview).toBe('Review the open PRs')
38+
expect(sessions[0]?.derivedTitle).toBe('Review the open PRs')
39+
expect(sessions[1]?.derivedTitle).toBe('Fix Docker publish')
40+
})
41+
})

src/screens/chat/utils.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type {
66
SessionTitleStatus,
77
ToolCallContent,
88
} from './types'
9+
import { stripWorkspaceDirective } from '../../lib/workspace-message-scope'
910

1011
export function deriveFriendlyIdFromKey(key: string | undefined): string {
1112
if (!key) return 'main'
@@ -52,7 +53,7 @@ function stripChannelPrefix(text: string): string {
5253
* and [Telegram/Signal/etc ...] headers, leaving just the user's text.
5354
*/
5455
function cleanUserText(raw: string): string {
55-
let text = raw
56+
let text = stripWorkspaceDirective(raw)
5657

5758
// Remove "Conversation info (untrusted metadata):" headers + JSON block
5859
// Format: "Conversation info (untrusted metadata):\n```json\n{...}\n```\n\n"
@@ -226,15 +227,15 @@ export function normalizeSessions(
226227
: undefined
227228
const explicitTitle =
228229
typeof session.title === 'string' && session.title.trim().length > 0
229-
? session.title.trim()
230+
? cleanUserText(session.title.trim()) || session.title.trim()
230231
: undefined
231232
const derivedTitle =
232233
typeof session.derivedTitle === 'string' &&
233234
session.derivedTitle.trim().length > 0
234-
? session.derivedTitle.trim()
235+
? cleanUserText(session.derivedTitle.trim()) || session.derivedTitle.trim()
235236
: typeof session.preview === 'string' &&
236237
session.preview.trim().length > 0
237-
? session.preview.trim()
238+
? cleanUserText(session.preview.trim()) || session.preview.trim()
238239
: undefined
239240
const titleStatus = deriveTitleStatus(
240241
label,
@@ -261,7 +262,10 @@ export function normalizeSessions(
261262
titleStatus,
262263
titleSource,
263264
titleError: session.titleError ?? null,
264-
preview: session.preview ?? null,
265+
preview:
266+
typeof session.preview === 'string'
267+
? cleanUserText(session.preview) || session.preview.trim() || null
268+
: session.preview ?? null,
265269
}
266270
})
267271
}

vite.config.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,10 @@ const config = defineConfig(({ mode, command }) => {
478478
// 2. $PORT env var (for containers, reverse proxies, WhatsApp bridge collisions, etc. — see #96)
479479
// 3. default 3000 (matches README/docs/docker-compose expectations)
480480
port: process.env.PORT ? Number(process.env.PORT) : 3000,
481-
strictPort: false, // allow fallback if port is taken, but log clearly
481+
// Managed Workspace launchers expect a stable port. Fail loudly instead
482+
// of silently hopping to 3001+ so launchctl/service health matches the
483+
// actual listening socket.
484+
strictPort: true,
482485
allowedHosts: true,
483486
watch: {
484487
ignored: [

0 commit comments

Comments
 (0)