crawd 0.9.0 → 0.9.2

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "crawd",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
4
  "description": "CLI for crawd.bot - AI agent livestreaming platform",
5
5
  "type": "module",
6
6
  "types": "./dist/types.d.ts",
@@ -23,6 +23,15 @@
23
23
  "bin": {
24
24
  "crawd": "./dist/cli.js"
25
25
  },
26
+ "scripts": {
27
+ "dev": "tsx src/cli.ts",
28
+ "build": "tsup src/cli.ts src/types.ts src/client.ts --format esm --dts --clean",
29
+ "build:backend": "tsup src/backend/index.ts --format esm --out-dir dist/backend --clean",
30
+ "build:plugin": "tsup src/plugin.ts --format esm --out-dir dist --dts",
31
+ "build:all": "pnpm build && pnpm build:backend",
32
+ "test": "vitest run",
33
+ "typecheck": "tsc --noEmit"
34
+ },
26
35
  "keywords": [
27
36
  "ai",
28
37
  "agent",
@@ -75,14 +84,5 @@
75
84
  "openclaw": {
76
85
  "optional": true
77
86
  }
78
- },
79
- "scripts": {
80
- "dev": "tsx src/cli.ts",
81
- "build": "tsup src/cli.ts src/types.ts src/client.ts --format esm --dts --clean",
82
- "build:backend": "tsup src/backend/index.ts --format esm --out-dir dist/backend --clean",
83
- "build:plugin": "tsup src/plugin.ts --format esm --out-dir dist --dts",
84
- "build:all": "pnpm build && pnpm build:backend",
85
- "test": "vitest run",
86
- "typecheck": "tsc --noEmit"
87
87
  }
88
- }
88
+ }
@@ -47,6 +47,7 @@ livestream_talk text="your message"
47
47
  - **Be vocal.** Silence is dead air. If you're browsing, comment on what you see. If you're scrolling a timeline, react to posts. Quote things, make jokes, share opinions. Viewers came for your commentary, not to watch you silently click through pages.
48
48
  - **React to content on screen.** When you see a tweet, article, or something interesting — talk about it. "look at this, YC president is posting about crypto, someone is about to get rugged" is way better than clicking past it in silence.
49
49
  - **Narrate your thoughts, not your actions.** Viewers can see what you're doing — tell them what you're *thinking*. Don't say "opening twitter", say "lets see what twitter is mad about today".
50
+ - **Never mention your internal systems.** Plans, steps, coordinators, nudges, vibes, autonomy modes — these are all invisible infrastructure. Never say things like "marking that step as done", "moving to the next step", "my plan is to...", "step 2 complete". Viewers should see a natural, self-directed agent — not a bot executing a checklist. Just do things and talk about what you're doing naturally.
50
51
 
51
52
  ## Response Protocol
52
53
 
@@ -113,6 +114,7 @@ If you receive chat with no active plan while in plan mode, create one with `pla
113
114
  - **Abandon plans that aren't working.** If a step is blocked or no longer relevant, use `plan_abandon` and create a new plan or go idle.
114
115
  - **Plans from chat:** When viewers suggest things to do, incorporate their ideas into a plan.
115
116
  - **You can replace plans.** Calling `plan_set` while a plan is active automatically abandons the old one.
117
+ - **Plans are invisible to viewers.** Never mention plans, steps, or progress to the audience. Don't say "completing step 3" or "my plan is to..." — just do the thing and talk about it naturally. The plan system is internal scaffolding, not content.
116
118
 
117
119
  ## Coordinator Signals
118
120
 
@@ -123,7 +125,7 @@ All coordinator messages use the `[CRAWD:*]` prefix:
123
125
  | `[CRAWD:CHAT]` | Batch of viewer chat messages. Reply with `livestream_reply`. |
124
126
  | `[CRAWD:VIBE]` | Autonomous activity nudge. Do something visible on stream. |
125
127
  | `[CRAWD:PLAN]` | Plan mode nudge. Shows current plan progress and next step. Work on it. |
126
- | `[CRAWD:MISALIGNED]` | Your previous response violated the protocol. You replied with plaintext instead of using a tool. Fix your behavior — use `livestream_reply` or `livestream_talk`, then respond with `LIVESTREAM_REPLIED`. |
128
+ | `[CRAWD:MISALIGNED]` | Your previous response violated the protocol. You replied with plaintext instead of using a tool. Fix your behavior silently do NOT apologize or mention this on stream. Just respond with `NO_REPLY` and do better next time. |
127
129
 
128
130
  ## Safety (non-negotiable)
129
131
 
@@ -506,6 +506,102 @@ export class OneShotGateway {
506
506
  })
507
507
  })
508
508
  }
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
+ }
509
605
  }
510
606
 
511
607
  /**
@@ -541,6 +637,7 @@ export class Coordinator {
541
637
  private buffer: ChatMessage[] = []
542
638
  private timer: NodeJS.Timeout | null = null
543
639
  private triggerFn: TriggerAgentFn
640
+ private compactFn?: () => Promise<void>
544
641
  private onEvent?: (event: CoordinatorEvent) => void
545
642
  /** Timestamp when coordinator was created (used to filter old messages on restart) */
546
643
  private readonly startedAt: number
@@ -620,6 +717,11 @@ export class Coordinator {
620
717
  this.onEvent = callback
621
718
  }
622
719
 
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
+
623
725
  private emit(event: CoordinatorEvent): void {
624
726
  this.onEvent?.(event)
625
727
  }
@@ -695,9 +797,10 @@ export class Coordinator {
695
797
 
696
798
  /** Compact the agent's session context before sleeping to free stale history */
697
799
  private compactSession(): void {
800
+ if (!this.compactFn) return
698
801
  this._gatewayQueue = this._gatewayQueue.then(async () => {
699
802
  try {
700
- await this.triggerFn('/compact')
803
+ await this.compactFn!()
701
804
  this.logger.log('[Coordinator] Session compacted before sleep')
702
805
  } catch (err) {
703
806
  this.logger.error('[Coordinator] Failed to compact session:', err)
@@ -912,6 +1015,7 @@ export class Coordinator {
912
1015
  lines.push(`Work on step ${nextStepIdx}. Use plan_step_done when complete.`)
913
1016
  }
914
1017
  lines.push('You can use plan_abandon to drop this plan, or plan_set to replace it.')
1018
+ lines.push('IMPORTANT: Never mention plans, steps, or progress on stream. Just do the thing naturally.')
915
1019
  lines.push('Respond with LIVESTREAM_REPLIED after speaking, or NO_REPLY if you have nothing to say.')
916
1020
 
917
1021
  return lines.join('\n')
@@ -1127,7 +1231,7 @@ export class Coordinator {
1127
1231
  `[CRAWD:MISALIGNED] Your previous response was plaintext: ${leaked}. ` +
1128
1232
  `Plaintext is NEVER visible to viewers. You MUST use livestream_reply or livestream_talk tool calls to speak. ` +
1129
1233
  `After using a tool, respond with LIVESTREAM_REPLIED. If you have nothing to say, respond with NO_REPLY. ` +
1130
- `Do not respond with any other text.`
1234
+ `Do NOT apologize or mention this correction on stream — viewers cannot see these messages and it makes no sense to them. Just fix your behavior silently.`
1131
1235
  )
1132
1236
  } catch (err) {
1133
1237
  this.logger.error('[Coordinator] Misalignment correction failed:', err)
@@ -1186,7 +1290,7 @@ export class Coordinator {
1186
1290
  // In plan mode with no active plan, instruct agent to create one
1187
1291
  let planInstruction = ''
1188
1292
  if (this.config.autonomyMode === 'plan' && !this.hasPendingPlanSteps()) {
1189
- planInstruction = '\n\nYou are in plan mode. Create a plan using plan_set based on these messages or your own ideas, then start working on it.'
1293
+ planInstruction = '\n\nYou are in plan mode. Create a plan using plan_set based on these messages or your own ideas, then start working on it. Never mention plans or steps on stream — just do things naturally.'
1190
1294
  }
1191
1295
 
1192
1296
  return `${header}\n${lines.join('\n')}${instruction}${planInstruction}`
@@ -272,6 +272,10 @@ export class CrawdBackend {
272
272
  coordConfig,
273
273
  )
274
274
 
275
+ this.coordinator.setCompactFn(async () => {
276
+ await gateway.compactSession()
277
+ })
278
+
275
279
  this.coordinator.setOnEvent((event: CoordinatorEvent) => {
276
280
  if (event.type === 'stateChange') {
277
281
  this.io.emit('crawd:status', { status: event.to })