fix: salvage EJClaw runtime hardening

- add dist freshness guard to deploy flow
- preserve message source provenance for IPC injection
- harden Codex usage fallback and workspace package manager detection
- document owner branch return protocol
This commit is contained in:
ejclaw
2026-04-24 15:02:09 +09:00
parent a36c8a5f07
commit e036521054
19 changed files with 1059 additions and 51 deletions

View File

@@ -19,6 +19,7 @@ export function applyBaseSchema(database: Database): void {
seq INTEGER,
is_from_me INTEGER,
is_bot_message INTEGER DEFAULT 0,
message_source_kind TEXT NOT NULL DEFAULT 'human',
PRIMARY KEY (id, chat_jid),
FOREIGN KEY (chat_jid) REFERENCES chats(jid)
);

View File

@@ -1,5 +1,9 @@
import { Database } from 'bun:sqlite';
import {
inferMessageSourceKindFromBotFlag,
normalizeMessageSourceKind,
} from '../message-source.js';
import { NewMessage } from '../types.js';
export interface ChatInfo {
@@ -14,12 +18,18 @@ function normalizeMessageRow(
row: NewMessage & {
is_from_me?: boolean | number;
is_bot_message?: boolean | number;
message_source_kind?: unknown;
},
): NewMessage {
const isBotMessage = !!row.is_bot_message;
return {
...row,
is_from_me: !!row.is_from_me,
is_bot_message: !!row.is_bot_message,
is_bot_message: isBotMessage,
message_source_kind: normalizeMessageSourceKind(
row.message_source_kind,
inferMessageSourceKindFromBotFlag(isBotMessage),
),
};
}
@@ -106,15 +116,16 @@ export function storeMessageInDatabase(
database
.prepare(
`INSERT INTO messages (
id, chat_jid, sender, sender_name, content, timestamp, seq, is_from_me, is_bot_message
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
id, chat_jid, sender, sender_name, content, timestamp, seq, is_from_me, is_bot_message, message_source_kind
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(id, chat_jid) DO UPDATE SET
sender = excluded.sender,
sender_name = excluded.sender_name,
content = excluded.content,
timestamp = excluded.timestamp,
is_from_me = excluded.is_from_me,
is_bot_message = excluded.is_bot_message`,
is_bot_message = excluded.is_bot_message,
message_source_kind = excluded.message_source_kind`,
)
.run(
msg.id,
@@ -126,6 +137,10 @@ export function storeMessageInDatabase(
seq,
msg.is_from_me ? 1 : 0,
msg.is_bot_message ? 1 : 0,
normalizeMessageSourceKind(
msg.message_source_kind,
inferMessageSourceKindFromBotFlag(msg.is_bot_message),
),
);
})();
}
@@ -142,7 +157,7 @@ export function getNewMessagesFromDatabase(
const placeholders = jids.map(() => '?').join(',');
const sql = `
SELECT * FROM (
SELECT id, chat_jid, sender, sender_name, content, timestamp, is_from_me, is_bot_message
SELECT id, chat_jid, sender, sender_name, content, timestamp, is_from_me, is_bot_message, message_source_kind
FROM messages
WHERE timestamp > ? AND chat_jid IN (${placeholders})
AND content NOT LIKE ?
@@ -178,7 +193,7 @@ export function getMessagesSinceFromDatabase(
): NewMessage[] {
const sql = `
SELECT * FROM (
SELECT id, chat_jid, sender, sender_name, content, timestamp, is_from_me, is_bot_message
SELECT id, chat_jid, sender, sender_name, content, timestamp, is_from_me, is_bot_message, message_source_kind
FROM messages
WHERE chat_jid = ? AND timestamp > ?
AND content NOT LIKE ?
@@ -238,7 +253,7 @@ export function getNewMessagesBySeqFromDatabase(
const placeholders = jids.map(() => '?').join(',');
const sql = `
SELECT id, chat_jid, sender, sender_name, content, timestamp, seq, is_from_me, is_bot_message
SELECT id, chat_jid, sender, sender_name, content, timestamp, seq, is_from_me, is_bot_message, message_source_kind
FROM messages
WHERE seq > ? AND chat_jid IN (${placeholders})
AND content NOT LIKE ?
@@ -273,7 +288,7 @@ export function getMessagesSinceSeqFromDatabase(
): NewMessage[] {
const sinceSeq = normalizeSeqCursor(sinceSeqCursor);
const sql = `
SELECT id, chat_jid, sender, sender_name, content, timestamp, seq, is_from_me, is_bot_message
SELECT id, chat_jid, sender, sender_name, content, timestamp, seq, is_from_me, is_bot_message, message_source_kind
FROM messages
WHERE chat_jid = ? AND seq > ?
AND content NOT LIKE ?
@@ -300,7 +315,7 @@ export function getRecentChatMessagesFromDatabase(
): NewMessage[] {
const sql = `
SELECT * FROM (
SELECT id, chat_jid, sender, sender_name, content, timestamp, is_from_me, is_bot_message
SELECT id, chat_jid, sender, sender_name, content, timestamp, is_from_me, is_bot_message, message_source_kind
FROM messages
WHERE chat_jid = ?
AND content != '' AND content IS NOT NULL

View File

@@ -0,0 +1,43 @@
import type { Database } from 'bun:sqlite';
import { tableHasColumn } from './helpers.js';
import type { SchemaMigrationDefinition } from './types.js';
export const MESSAGE_SOURCE_KIND_MIGRATION: SchemaMigrationDefinition = {
version: 13,
name: 'message_source_kind',
apply(database: Database) {
const addedColumn = !tableHasColumn(
database,
'messages',
'message_source_kind',
);
if (addedColumn) {
database.exec(`
ALTER TABLE messages
ADD COLUMN message_source_kind TEXT NOT NULL DEFAULT 'human'
`);
}
const invalidOnlyWhere = `
WHERE message_source_kind IS NULL
OR message_source_kind = ''
OR message_source_kind NOT IN (
'human',
'bot',
'trusted_external_bot',
'ipc_injected_human',
'ipc_injected_bot'
)
`;
database.exec(`
UPDATE messages
SET message_source_kind = CASE
WHEN is_bot_message = 1 THEN 'bot'
ELSE 'human'
END
${addedColumn ? '' : invalidOnlyWhere}
`);
},
};

View File

@@ -12,6 +12,7 @@ import { PAIRED_WORKSPACE_PROJECT_SCHEMA_CLEANUP_MIGRATION } from './009_paired-
import { PAIRED_TURN_PROVENANCE_UPGRADE_MIGRATION } from './010_paired-turn-provenance-upgrade.js';
import { OWNER_FAILURE_COUNT_MIGRATION } from './011_owner-failure-count.js';
import { PAIRED_VERDICT_AND_STEP_TELEMETRY_MIGRATION } from './012_paired-verdict-and-step-telemetry.js';
import { MESSAGE_SOURCE_KIND_MIGRATION } from './013_message-source-kind.js';
import type {
SchemaMigrationArgs,
SchemaMigrationDefinition,
@@ -32,6 +33,7 @@ const ORDERED_SCHEMA_MIGRATIONS: readonly SchemaMigrationDefinition[] = [
PAIRED_TURN_PROVENANCE_UPGRADE_MIGRATION,
OWNER_FAILURE_COUNT_MIGRATION,
PAIRED_VERDICT_AND_STEP_TELEMETRY_MIGRATION,
MESSAGE_SOURCE_KIND_MIGRATION,
];
function ensureSchemaMigrationsTable(database: Database): void {