crawd 0.9.4 → 0.9.6
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/package.json
CHANGED
package/skills/crawd/SKILL.md
CHANGED
|
@@ -9,8 +9,10 @@ metadata: {"emoji": "🟠", "requires.env": ["OPENCLAW_GATEWAY_TOKEN"], "require
|
|
|
9
9
|
Viewers see your screen in real time. Every click, every page, every tab — it's all on stream. Act like someone is watching, because they are.
|
|
10
10
|
|
|
11
11
|
**Screen awareness:**
|
|
12
|
+
- **Take screenshots constantly.** If your model supports vision, screenshot is your best friend. Take one before responding to chat, after navigating to a new page, after scrolling, when you wake up, when something changes on screen. You cannot see what viewers see unless you screenshot. This is how you stay aware of your own stream.
|
|
12
13
|
- **Check your screen when you wake up.** Take a screenshot before doing anything — you may have gone to sleep with tabs open, notifications visible, or pages loaded. Viewers can see all of this and will reference it in chat. If you don't look at your screen first, you won't understand what they're talking about.
|
|
13
14
|
- **Check your screen before responding to chat.** Viewers are looking at the same screen you are. Their messages often reference what's visible — a notification count, a tweet on screen, a video playing. Screenshot first so you have the same context they do.
|
|
15
|
+
- **Screenshot after actions.** After clicking, scrolling, or navigating — screenshot to see the result. Don't blindly chain actions. Look at what happened.
|
|
14
16
|
- Keep your browser tidy. Close tabs you're not using — empty tabs, old searches, finished pages. Viewers see clutter.
|
|
15
17
|
- When you finish with something, close it. Don't leave 15 tabs open.
|
|
16
18
|
- Think of your screen as your stage. What's on it matters.
|
|
@@ -330,24 +330,24 @@ describe('Coordinator — Plan Mode', () => {
|
|
|
330
330
|
})
|
|
331
331
|
})
|
|
332
332
|
|
|
333
|
-
describe('chat batch
|
|
334
|
-
it('
|
|
333
|
+
describe('chat batch format', () => {
|
|
334
|
+
it('includes END OF CHAT delimiter', () => {
|
|
335
335
|
const coord = createCoordinator()
|
|
336
|
-
// No plan set
|
|
337
336
|
|
|
338
|
-
const batch = coord.formatBatch([makeChatMessage('
|
|
339
|
-
expect(batch).toContain('
|
|
340
|
-
expect(batch).toContain('plan_set')
|
|
337
|
+
const batch = coord.formatBatch([makeChatMessage('hello')])
|
|
338
|
+
expect(batch).toContain('[END OF CHAT]')
|
|
339
|
+
expect(batch).not.toContain('plan_set')
|
|
341
340
|
|
|
342
341
|
coord.stop()
|
|
343
342
|
})
|
|
344
343
|
|
|
345
|
-
it('does not
|
|
344
|
+
it('does not include plan instruction in chat batch', () => {
|
|
346
345
|
const coord = createCoordinator()
|
|
347
|
-
|
|
346
|
+
// No plan set, but chat batch should NOT include plan instruction
|
|
348
347
|
|
|
349
|
-
const batch = coord.formatBatch([makeChatMessage('
|
|
348
|
+
const batch = coord.formatBatch([makeChatMessage('do something cool')])
|
|
350
349
|
expect(batch).not.toContain('plan mode')
|
|
350
|
+
expect(batch).not.toContain('plan_set')
|
|
351
351
|
|
|
352
352
|
coord.stop()
|
|
353
353
|
})
|
|
@@ -507,101 +507,6 @@ export class OneShotGateway {
|
|
|
507
507
|
})
|
|
508
508
|
}
|
|
509
509
|
|
|
510
|
-
/** Compact a session via the gateway's sessions.compact method */
|
|
511
|
-
async compactSession(maxLines = 400): Promise<{ compacted: boolean }> {
|
|
512
|
-
return new Promise((resolve, reject) => {
|
|
513
|
-
const ws = new WebSocket(this.url)
|
|
514
|
-
const authId = randomUUID()
|
|
515
|
-
const reqId = randomUUID()
|
|
516
|
-
let settled = false
|
|
517
|
-
|
|
518
|
-
const timeout = setTimeout(() => {
|
|
519
|
-
if (!settled) {
|
|
520
|
-
settled = true
|
|
521
|
-
ws.close()
|
|
522
|
-
reject(new Error('One-shot compact request timed out (30s)'))
|
|
523
|
-
}
|
|
524
|
-
}, 30_000)
|
|
525
|
-
|
|
526
|
-
const finish = (result?: { compacted: boolean }, error?: Error) => {
|
|
527
|
-
if (settled) return
|
|
528
|
-
settled = true
|
|
529
|
-
clearTimeout(timeout)
|
|
530
|
-
ws.close()
|
|
531
|
-
if (error) {
|
|
532
|
-
console.error(`[OneShotGateway] compact error: ${error.message}`)
|
|
533
|
-
reject(error)
|
|
534
|
-
} else {
|
|
535
|
-
resolve(result ?? { compacted: false })
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
const sendConnect = () => {
|
|
540
|
-
ws.send(JSON.stringify({
|
|
541
|
-
type: 'req',
|
|
542
|
-
id: authId,
|
|
543
|
-
method: 'connect',
|
|
544
|
-
params: {
|
|
545
|
-
minProtocol: 3,
|
|
546
|
-
maxProtocol: 3,
|
|
547
|
-
client: {
|
|
548
|
-
id: 'gateway-client',
|
|
549
|
-
version: '1.0.0',
|
|
550
|
-
platform: 'node',
|
|
551
|
-
mode: 'backend',
|
|
552
|
-
},
|
|
553
|
-
auth: this.token ? { token: this.token } : {},
|
|
554
|
-
},
|
|
555
|
-
}))
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
ws.on('message', (data) => {
|
|
559
|
-
try {
|
|
560
|
-
const frame = JSON.parse(data.toString()) as GatewayFrame
|
|
561
|
-
|
|
562
|
-
if (frame.type === 'event' && (frame as any).event === 'connect.challenge') {
|
|
563
|
-
sendConnect()
|
|
564
|
-
return
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
if (frame.type === 'res' && frame.id === authId) {
|
|
568
|
-
if (frame.error) {
|
|
569
|
-
finish(undefined, new Error(`Gateway auth failed: ${frame.error.message}`))
|
|
570
|
-
return
|
|
571
|
-
}
|
|
572
|
-
ws.send(JSON.stringify({
|
|
573
|
-
type: 'req',
|
|
574
|
-
id: reqId,
|
|
575
|
-
method: 'sessions.compact',
|
|
576
|
-
params: {
|
|
577
|
-
key: this.sessionKey,
|
|
578
|
-
maxLines,
|
|
579
|
-
},
|
|
580
|
-
}))
|
|
581
|
-
} else if (frame.type === 'res' && frame.id === reqId) {
|
|
582
|
-
if (frame.error) {
|
|
583
|
-
finish(undefined, new Error(`sessions.compact failed: ${frame.error.message}`))
|
|
584
|
-
return
|
|
585
|
-
}
|
|
586
|
-
const payload = frame.payload as any
|
|
587
|
-
finish({ compacted: payload?.compacted === true })
|
|
588
|
-
}
|
|
589
|
-
} catch {
|
|
590
|
-
// Parse error — ignore
|
|
591
|
-
}
|
|
592
|
-
})
|
|
593
|
-
|
|
594
|
-
ws.on('error', (err) => {
|
|
595
|
-
finish(undefined, err instanceof Error ? err : new Error(String(err)))
|
|
596
|
-
})
|
|
597
|
-
|
|
598
|
-
ws.on('close', () => {
|
|
599
|
-
if (!settled) {
|
|
600
|
-
finish(undefined, new Error('WebSocket closed unexpectedly'))
|
|
601
|
-
}
|
|
602
|
-
})
|
|
603
|
-
})
|
|
604
|
-
}
|
|
605
510
|
}
|
|
606
511
|
|
|
607
512
|
/**
|
|
@@ -637,7 +542,6 @@ export class Coordinator {
|
|
|
637
542
|
private buffer: ChatMessage[] = []
|
|
638
543
|
private timer: NodeJS.Timeout | null = null
|
|
639
544
|
private triggerFn: TriggerAgentFn
|
|
640
|
-
private compactFn?: () => Promise<void>
|
|
641
545
|
private onEvent?: (event: CoordinatorEvent) => void
|
|
642
546
|
/** Timestamp when coordinator was created (used to filter old messages on restart) */
|
|
643
547
|
private readonly startedAt: number
|
|
@@ -717,11 +621,6 @@ export class Coordinator {
|
|
|
717
621
|
this.onEvent = callback
|
|
718
622
|
}
|
|
719
623
|
|
|
720
|
-
/** Set the compact function (uses gateway sessions.compact instead of sending /compact as agent message) */
|
|
721
|
-
setCompactFn(fn: () => Promise<void>): void {
|
|
722
|
-
this.compactFn = fn
|
|
723
|
-
}
|
|
724
|
-
|
|
725
624
|
private emit(event: CoordinatorEvent): void {
|
|
726
625
|
this.onEvent?.(event)
|
|
727
626
|
}
|
|
@@ -792,20 +691,6 @@ export class Coordinator {
|
|
|
792
691
|
|
|
793
692
|
this.stopVibeLoop()
|
|
794
693
|
this.cancelPlanNudge()
|
|
795
|
-
this.compactSession()
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
/** Compact the agent's session context before sleeping to free stale history */
|
|
799
|
-
private compactSession(): void {
|
|
800
|
-
if (!this.compactFn) return
|
|
801
|
-
this._gatewayQueue = this._gatewayQueue.then(async () => {
|
|
802
|
-
try {
|
|
803
|
-
await this.compactFn!()
|
|
804
|
-
this.logger.log('[Coordinator] Session compacted before sleep')
|
|
805
|
-
} catch (err) {
|
|
806
|
-
this.logger.error('[Coordinator] Failed to compact session:', err)
|
|
807
|
-
}
|
|
808
|
-
}).catch(() => {})
|
|
809
694
|
}
|
|
810
695
|
|
|
811
696
|
/** Signal that the agent is speaking (via tool call) — keeps coordinator awake */
|
|
@@ -1288,12 +1173,6 @@ export class Coordinator {
|
|
|
1288
1173
|
? '\n(To reply to a specific message, prefix with its ID: [msgId] your reply)'
|
|
1289
1174
|
: ''
|
|
1290
1175
|
|
|
1291
|
-
|
|
1292
|
-
let planInstruction = ''
|
|
1293
|
-
if (this.config.autonomyMode === 'plan' && !this.hasPendingPlanSteps()) {
|
|
1294
|
-
planInstruction = '\n\nCreate a plan using plan_set. Be creative and proactive — come up with something YOU want to do, not just "reply to chat". Browse the web, go down rabbit holes, check X/HN/YouTube, find weird stuff, have opinions. Each step should be a concrete action. Think aloud on stream about what you\'re doing. Never mention plans or steps to viewers.'
|
|
1295
|
-
}
|
|
1296
|
-
|
|
1297
|
-
return `${header}\n${lines.join('\n')}${instruction}${planInstruction}`
|
|
1176
|
+
return `${header}\n${lines.join('\n')}\n[END OF CHAT]${instruction}`
|
|
1298
1177
|
}
|
|
1299
1178
|
}
|
package/src/backend/server.ts
CHANGED
|
@@ -272,10 +272,6 @@ export class CrawdBackend {
|
|
|
272
272
|
coordConfig,
|
|
273
273
|
)
|
|
274
274
|
|
|
275
|
-
this.coordinator.setCompactFn(async () => {
|
|
276
|
-
await gateway.compactSession()
|
|
277
|
-
})
|
|
278
|
-
|
|
279
275
|
this.coordinator.setOnEvent((event: CoordinatorEvent) => {
|
|
280
276
|
if (event.type === 'stateChange') {
|
|
281
277
|
this.io.emit('crawd:status', { status: event.to })
|