sync: update from private repo (7d64ee2)
CI / build-and-test (push) Has been cancelled

This commit is contained in:
oss-sync
2026-06-05 05:42:11 +00:00
parent c526adddc2
commit 02c7dfdd83
16 changed files with 205 additions and 35 deletions
+1
View File
@@ -41,6 +41,7 @@ const TOOL_DOC_ALIASES: Record<string, string> = {
xuserposts: 'xsearch',
xpostdetail: 'xsearch',
xfetchcardmedia: 'xsearch',
xtimeline: 'xsearch',
// youtube.ts をまとめる
searchyoutube: 'getyoutubetranscript',
// maps.ts をまとめる
+1
View File
@@ -9,6 +9,7 @@ export const RAW_SAVE_TOOLS = new Set([
'XSearch',
'XUserPosts',
'XPostDetail',
'XTimeline',
'BrowseWeb',
'GetYouTubeTranscript',
'SearchYouTube',
+1 -1
View File
@@ -126,7 +126,7 @@ function mkStubSubsystem() {
setWindow: vi.fn(),
on: vi.fn(),
};
const client = { end: vi.fn() };
const client = { end: vi.fn(), on: vi.fn() };
const openShellChannel = vi.fn().mockResolvedValue({
channel,
client,
+7 -2
View File
@@ -246,6 +246,7 @@ async function ensureSessionInternal(
// Open the channel. On failure clear the PEM and bail.
let channel: import('ssh2').ClientChannel;
let client: import('ssh2').Client;
let hostFingerprint: string;
try {
const shellResult = await sub.openShellChannel({
@@ -266,6 +267,7 @@ async function ensureSessionInternal(
timeoutMs: sub.config.callTimeoutSeconds * 1000,
});
channel = shellResult.channel;
client = shellResult.client;
hostFingerprint = shellResult.hostFingerprint;
} catch (e) {
clearBuffer(pemBuf);
@@ -292,8 +294,10 @@ async function ensureSessionInternal(
return err(`SshConsoleEnsure: failed to open shell channel: ${(e as Error).message}`);
}
// Build the session and register it. From here on the channel + PEM
// belong to the session; we don't clear them on the happy path.
// Build the session and register it. From here on the channel + client
// + PEM belong to the session; we don't clear them on the happy path.
// The session ends the client (and thus releases the PEM-bound
// connection) when it closes.
const session = new ConsoleSession({
localTaskId,
connectionId,
@@ -303,6 +307,7 @@ async function ensureSessionInternal(
rows,
scrollbackCap: sub.config.console.scrollbackBytes,
channel,
client,
auditRepo: sub.auditRepo,
});
sub.sessionRegistry.register(session);
+51
View File
@@ -129,11 +129,30 @@ const XFETCHCARDMEDIA_DEF: ToolDef = {
},
};
const XTIMELINE_DEF: ToolDef = {
type: 'function',
function: {
name: 'XTimeline',
description: 'ログイン中アカウントのホームタイムライン(For You / フォロー中)を取得する(twitter-cli 経由、認証 Cookie 設定が必要)。詳細は ReadToolDoc({ name: "XTimeline" })。',
parameters: {
type: 'object',
properties: {
tab: { type: 'string', enum: ['for_you', 'following'], description: 'for_you (デフォルト) / following' },
limit: { type: 'number', description: '件数 (デフォルト: 20, 最大: 50)' },
full_text: { type: 'boolean', description: '長文の省略を避ける' },
compact: { type: 'boolean', description: 'token 節約向けの compact 出力' },
output_path: { type: 'string', description: '任意: output/x/ 配下に保存する相対パス' },
},
},
},
};
export const TOOL_DEFS: Record<string, ToolDef> = {
XSearch: XSEARCH_DEF,
XUserPosts: XUSERPOSTS_DEF,
XPostDetail: XPOSTDETAIL_DEF,
XFetchCardMedia: XFETCHCARDMEDIA_DEF,
XTimeline: XTIMELINE_DEF,
};
type XHistoryRecord = {
@@ -608,6 +627,36 @@ async function executeXUserPosts(input: Record<string, unknown>, ctx: ToolContex
return runTwitterCli('XUserPosts', args, ctx, outputPath);
}
async function executeXTimeline(input: Record<string, unknown>, ctx: ToolContext): Promise<ToolResult> {
const tab = String(input['tab'] ?? 'for_you');
const following = tab === 'following';
const args = ['feed', '--max', String(clampLimit(input['limit'], 20)), '--yaml'];
if (following) args.push('-t', 'following');
maybePushFlag(args, input['full_text'], '--full-text');
maybePushFlag(args, input['compact'], '--compact');
let outputPath: string | null;
try {
outputPath = resolveOptionalOutputPath(ctx, input['output_path']);
} catch (err) {
return { output: `XTimeline error: ${(err as Error).message}`, isError: true };
}
const result = await runTwitterCli('XTimeline', args, ctx, outputPath);
if (result.isError) return result;
const posts = parseXPostsFromYaml(result.output);
if (posts.length > 0) {
const refId = `xposts-${Date.now()}`;
const structuredBlocks: StructuredBlock[] = [{
refId,
type: 'x_posts',
title: following ? 'X タイムライン(フォロー中)' : 'X タイムライン(For You',
data: { query: following ? 'following' : 'for_you', posts },
}];
return { output: `${result.output}\n\n[[embed:${refId}]]`, isError: false, structuredBlocks };
}
return result;
}
async function executeXPostDetail(input: Record<string, unknown>, ctx: ToolContext): Promise<ToolResult> {
const tweet = String(input['tweet'] ?? '').trim();
if (!tweet) return { output: 'XPostDetail error: tweet is required', isError: true };
@@ -862,6 +911,8 @@ export async function executeTool(
return executeXSearch(input, ctx);
case 'XUserPosts':
return executeXUserPosts(input, ctx);
case 'XTimeline':
return executeXTimeline(input, ctx);
case 'XPostDetail':
return executeXPostDetail(input, ctx);
case 'XFetchCardMedia':