# v0.61.1 Adaptations

The pi-integration-skill patches were written against pi-mono v0.58.4. This document records version-specific adaptations made when applying them to v0.61.1.

For general build environment setup (pnpm workspace, native modules, transitive dependency fixes), see [01-playfilo-db.md](../01-playfilo-db.md) section 1a.

---

## 1. `_persist()` Shim Placement Relative to `hasAssistant` Guard

**Problem (v0.58.4 vs v0.61.1):** In v0.58.4, `_persist()` was a straightforward file-append. In v0.61.1, it gained an early-return guard:

```typescript
const hasAssistant = this.fileEntries.some(e => e.type === "message" && e.message.role === "assistant");
if (!hasAssistant) {
    this.flushed = false;
    return; // <-- exits before any write
}
```

This guard delays JSONL writes until the first assistant message arrives. If the DAG shim were placed *after* this guard, early entries (user messages, model_change, thinking_level_change) would never be committed to the DAG.

**Adaptation:** The DAG shim is inserted AFTER the `if (!this.persist || !this.sessionFile) return;` check but BEFORE the `hasAssistant` guard. This means:
- DAG commits happen for every entry regardless of whether an assistant has responded
- JSONL writes still follow the original delayed-flush behavior
- The shim has its own try/catch so DAG failures never block JSONL persistence

```
_persist(entry) {
    if (!this.persist || !this.sessionFile) return;  // keep
    // --- DAG SHIM HERE ---                          // added
    const hasAssistant = ...                          // original guard
    // ... original JSONL logic ...                   // keep
}
```

---

## 2. Tobe Auto-Continue Handler Placement in `_processAgentEvent()`

**Problem (v0.58.4 vs v0.61.1):** In v0.58.4, the `agent_end` handler in `_processAgentEvent()` was simpler. In v0.61.1, it contains a retry system and auto-compaction:

```typescript
if (event.type === "agent_end" && this._lastAssistantMessage) {
    // retry logic (exponential backoff for overloaded/rate-limit errors)
    // compaction logic (auto-compact when context is large)
}
```

The tobe auto-continue must fire *before* this block. If retry runs first, it could remove the aborted assistant message and call `agent.continue()` with the wrong context. If compaction runs first, it would compact the stale pre-tobe context.

**Adaptation:** The tobe handler is inserted as a separate `agent_end` block *above* the existing one, with an early `return` to skip retry/compaction. Uses `setTimeout(0)` instead of `setTimeout(100)`:

```typescript
// --- PLAYFILO: Tobe auto-continue (must run before retry/compaction) ---
if (event.type === "agent_end" && this.agent.hasQueuedMessages()) {
    const tobeCtx = consumePendingTobeContext();
    if (tobeCtx) {
        setTimeout(() => {
            this.agent.replaceMessages(tobeCtx);
            this.agent.continue().catch(() => {});
        }, 0);
        return; // Skip retry/compaction — tobe takes over
    }
}

// Check auto-retry and auto-compaction after agent completes  (original)
if (event.type === "agent_end" && this._lastAssistantMessage) { ... }
```

**Why `setTimeout(0)` is safe:** When tobe calls `agent.abort()` inside `executeToolCalls`, the inner loop iterates once more (`hasMoreToolCalls` is still `true` from the tobe tool_call). `streamAssistantResponse()` is called with the aborted signal, returns `stopReason === "aborted"`, and `runLoop` exits via `return` — **before** `getFollowUpMessages()` is reached. The follow-up queue stays intact. By the time `_processAgentEvent(agent_end)` runs, both stale events (tool_result + aborted assistant) have already been processed through the event queue. `replaceMessages()` needs no delay.

`setTimeout(0)` fires at the first event loop yield, which in embedded environments (e.g. OpenClaw) ensures the continuation starts before post-prompt subscription teardown — eliminating a timing edge case where `setTimeout(100)` could fire after the subscription is already destroyed.

**Why `return` is safe:** The `return` prevents retry logic from treating the abort as a retryable error and compaction from running on the soon-to-be-replaced context.

**Why `hasQueuedMessages()` gate:** This ensures the block only activates when tobe actually queued follow-up messages (via `agent.followUp()`). Normal agent_end events without queued messages fall through to the original handler.

---

## 3. `switchSession()` SESSION_SWITCH Logging Around Extension Events

**Problem (v0.58.4 vs v0.61.1):** In v0.58.4, `switchSession()` was a simpler method. In v0.61.1, it has:
- `session_before_switch` extension event (can cancel the switch)
- Agent disconnect/abort/queue clearing
- `session_switch` extension event (post-switch notification)
- Model and thinking level restoration

The patch says to "wrap the existing `setSessionFile` call" with HEAD capture and action logging.

**Adaptation:** The logging wraps only `setSessionFile` + `sessionId` assignment, placed after the abort/cleanup but before message reload. This is the exact point where PI_HEAD transitions:

```typescript
// Set new session
const previousHead = getRef("PI_HEAD");           // capture before
this.sessionManager.setSessionFile(sessionPath);   // triggers DAG read hook, updates PI_HEAD
this.agent.sessionId = this.sessionManager.getSessionId();
const newHead = getRef("PI_HEAD");                 // capture after
if (newHead) {
    logAction("SESSION_SWITCH", previousHead, newHead,
        JSON.stringify({ trigger: "human", session: sessionPath }));
}
```

This correctly captures the HEAD transition even when `setSessionFile` triggers the DAG read hook (step 3c), which sets PI_HEAD to the target hash.

---

## 4. Defensive Null Checks

The null-safe guards documented here were already specified in the general patches:
- **Null-safe model extraction in `buildSessionContext()`** — see [03-session-manager.md](../03-session-manager.md) section 3f
- **Defensive null checks in `getSessionStats()` and `getContextUsage()`** — see [04-agent-session.md](../04-agent-session.md) section 4e

v0.61.1 confirmed these are necessary: DAG-loaded entries lack `usage`, `content`, `provider`, and `model` fields that Pi's native code assumes exist. The guards from the general patches apply without modification.
