@link-assistant/hive-mind 2.1.2 → 2.1.4
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/CHANGELOG.md +12 -0
- package/README.hi.md +3 -1
- package/README.md +3 -1
- package/README.ru.md +3 -1
- package/README.zh.md +3 -1
- package/package.json +1 -1
- package/src/agent.lib.mjs +132 -32
- package/src/bidirectional-interactive.lib.mjs +169 -71
- package/src/limits-i18n.lib.mjs +8 -0
- package/src/limits-subscription.lib.mjs +40 -1
- package/src/limits.lib.mjs +109 -150
- package/src/live-input-capabilities.lib.mjs +221 -0
- package/src/locales/en.lino +16 -0
- package/src/locales/hi.lino +16 -0
- package/src/locales/ru.lino +16 -0
- package/src/locales/zh.lino +16 -0
- package/src/solve.auto-merge-helpers.lib.mjs +66 -0
- package/src/solve.auto-merge.lib.mjs +34 -5
- package/src/solve.config.lib.mjs +10 -10
- package/src/solve.validation.lib.mjs +3 -1
package/src/limits.lib.mjs
CHANGED
|
@@ -14,7 +14,7 @@ import utc from 'dayjs/plugin/utc.js';
|
|
|
14
14
|
|
|
15
15
|
import { wrapDollarWithGhRetry as _wrapDollarWithGhRetry, execGhWithRetry } from './github-rate-limit.lib.mjs'; // rate-limit marker (#1726): gh API calls flow through $ wrapped by caller. execGhWithRetry adds transient-network retry (#1756).
|
|
16
16
|
import { formatLimitResetsAt, formatLimitResetsIn, formatLocalizedCurrentTime, formatLocalizedRelativeTime, formatLocalizedResetTime, localizeCompactDuration, lt, resolveLimitLocale } from './limits-i18n.lib.mjs';
|
|
17
|
-
import { formatSubscriptionLines, getCachedClaudeSubscription, getCachedCodexSubscription, getClaudeSubscriptionInfo, getCodexSubscriptionInfo } from './limits-subscription.lib.mjs';
|
|
17
|
+
import { formatSubscriptionHeading, formatSubscriptionLines, getCachedClaudeSubscription, getCachedCodexSubscription, getClaudeSubscriptionInfo, getCodexSubscriptionInfo } from './limits-subscription.lib.mjs';
|
|
18
18
|
export { getCachedClaudeSubscription, getCachedCodexSubscription, getClaudeSubscriptionInfo, getCodexSubscriptionInfo };
|
|
19
19
|
// Initialize dayjs plugins
|
|
20
20
|
dayjs.extend(utc);
|
|
@@ -280,6 +280,61 @@ function getLocalizedRelativeReset(window, options = {}, fallbackRelative = null
|
|
|
280
280
|
return formatRelativeTime(window?.resetsAt, options) || localizeCompactDuration(fallbackRelative, options);
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
+
function formatCodeBlock(content) {
|
|
284
|
+
const text = Array.isArray(content) ? content.join('\n') : String(content ?? '');
|
|
285
|
+
return '```\n' + (text.endsWith('\n') ? text : `${text}\n`) + '```';
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function formatPlainTitledCodeSection(section) {
|
|
289
|
+
const text = String(section ?? '').trimEnd();
|
|
290
|
+
if (!text) return '';
|
|
291
|
+
if (text.includes('```')) return text;
|
|
292
|
+
|
|
293
|
+
const lines = text.split('\n');
|
|
294
|
+
const title = lines.shift();
|
|
295
|
+
const body = lines.join('\n');
|
|
296
|
+
if (!body) return formatCodeBlock(title);
|
|
297
|
+
return `${title}\n\n${formatCodeBlock(body)}`;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function formatLimitWindowSection(label, window, periodHours, threshold, options = {}) {
|
|
301
|
+
const locale = resolveLimitLocale(options);
|
|
302
|
+
let section = `${label}\n`;
|
|
303
|
+
if (hasLimitPercentage(window)) {
|
|
304
|
+
const timePassed = calculateTimePassedPercentage(window.resetsAt, periodHours);
|
|
305
|
+
if (timePassed !== null) {
|
|
306
|
+
section += `${getProgressBar(timePassed)} ${timePassed}% ${lt('passed', {}, { locale })}\n`;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const pct = Math.floor(window.percentage);
|
|
310
|
+
const bar = getProgressBar(pct, threshold);
|
|
311
|
+
const suffix = pct >= threshold ? ' ⚠️' : ` ${lt('used', {}, { locale })}`;
|
|
312
|
+
section += `${bar} ${pct}%${suffix}\n`;
|
|
313
|
+
|
|
314
|
+
const resetTime = getLocalizedResetTime(window, { locale });
|
|
315
|
+
if (resetTime) {
|
|
316
|
+
const relativeTime = getLocalizedRelativeReset(window, { locale });
|
|
317
|
+
section += relativeTime ? `${formatLimitResetsIn(relativeTime, resetTime, { locale })}\n` : `${formatLimitResetsAt(resetTime, { locale })}\n`;
|
|
318
|
+
}
|
|
319
|
+
} else {
|
|
320
|
+
section += `${lt('na', {}, { locale })}\n`;
|
|
321
|
+
}
|
|
322
|
+
return section;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function hasPositivePercentage(value) {
|
|
326
|
+
if (value === null || value === undefined) return false;
|
|
327
|
+
const numeric = Number(value);
|
|
328
|
+
return Number.isFinite(numeric) && numeric > 0;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function hasPositiveCreditBalance(credits) {
|
|
332
|
+
if (!credits) return false;
|
|
333
|
+
if (credits.unlimited) return true;
|
|
334
|
+
const numeric = Number.parseFloat(String(credits.balance ?? '0'));
|
|
335
|
+
return Number.isFinite(numeric) && numeric > 0;
|
|
336
|
+
}
|
|
337
|
+
|
|
283
338
|
/**
|
|
284
339
|
* Get GitHub API rate limits by calling gh api rate_limit
|
|
285
340
|
* Returns rate limit info for core, search, graphql, and other resources
|
|
@@ -1061,118 +1116,51 @@ export function formatUsageMessage(usage, diskSpace = null, githubRateLimit = nu
|
|
|
1061
1116
|
sections.push(section);
|
|
1062
1117
|
}
|
|
1063
1118
|
|
|
1064
|
-
|
|
1065
|
-
|
|
1119
|
+
const claudeHeading = formatSubscriptionHeading('claude', subscription, { locale });
|
|
1120
|
+
const useShortClaudeLabels = Boolean(claudeHeading);
|
|
1121
|
+
const claudeSections = [];
|
|
1122
|
+
|
|
1123
|
+
// Claude limits section. When there's an error (e.g., auth expired), show it once and skip empty subsections.
|
|
1066
1124
|
if (claudeError) {
|
|
1067
|
-
|
|
1125
|
+
claudeSections.push(useShortClaudeLabels ? `${claudeError}\n` : `${lt('claude_limits', {}, { locale })}\n${claudeError}\n`);
|
|
1068
1126
|
} else {
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
if (
|
|
1073
|
-
|
|
1074
|
-
if (timePassed !== null) {
|
|
1075
|
-
const timeBar = getProgressBar(timePassed);
|
|
1076
|
-
sessionSection += `${timeBar} ${timePassed}% ${lt('passed', {}, { locale })}\n`;
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
// Use Math.floor so 100% only appears when usage is exactly 100%
|
|
1080
|
-
// See: https://github.com/link-assistant/hive-mind/issues/1133
|
|
1081
|
-
const pct = Math.floor(usage.currentSession.percentage);
|
|
1082
|
-
const bar = getProgressBar(pct, DISPLAY_THRESHOLDS.CLAUDE_5_HOUR_SESSION);
|
|
1083
|
-
const suffix = pct >= DISPLAY_THRESHOLDS.CLAUDE_5_HOUR_SESSION ? ' ⚠️' : ` ${lt('used', {}, { locale })}`;
|
|
1084
|
-
sessionSection += `${bar} ${pct}%${suffix}\n`;
|
|
1085
|
-
|
|
1086
|
-
const sessionResetTime = getLocalizedResetTime(usage.currentSession, { locale });
|
|
1087
|
-
if (sessionResetTime) {
|
|
1088
|
-
const relativeTime = getLocalizedRelativeReset(usage.currentSession, { locale });
|
|
1089
|
-
if (relativeTime) {
|
|
1090
|
-
sessionSection += `${formatLimitResetsIn(relativeTime, sessionResetTime, { locale })}\n`;
|
|
1091
|
-
} else {
|
|
1092
|
-
sessionSection += `${formatLimitResetsAt(sessionResetTime, { locale })}\n`;
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
} else {
|
|
1096
|
-
sessionSection += `${lt('na', {}, { locale })}\n`;
|
|
1127
|
+
const hasSonnetOnly = hasLimitPercentage(usage?.sonnetOnly);
|
|
1128
|
+
claudeSections.push(formatLimitWindowSection(useShortClaudeLabels ? lt('five_hour_limit_session', {}, { locale }) : lt('claude_5_hour_session', {}, { locale }), usage?.currentSession, 5, DISPLAY_THRESHOLDS.CLAUDE_5_HOUR_SESSION, { locale }));
|
|
1129
|
+
claudeSections.push(formatLimitWindowSection(useShortClaudeLabels && !hasSonnetOnly ? lt('current_week', {}, { locale }) : lt('current_week_all_models', {}, { locale }), usage?.allModels, 168, DISPLAY_THRESHOLDS.CLAUDE_WEEKLY, { locale }));
|
|
1130
|
+
if (hasSonnetOnly || !useShortClaudeLabels) {
|
|
1131
|
+
claudeSections.push(formatLimitWindowSection(lt('current_week_sonnet_only', {}, { locale }), usage?.sonnetOnly, 168, DISPLAY_THRESHOLDS.CLAUDE_WEEKLY, { locale }));
|
|
1097
1132
|
}
|
|
1098
|
-
sections.push(sessionSection);
|
|
1099
|
-
|
|
1100
|
-
// Current week (all models / seven_day)
|
|
1101
|
-
// Threshold: One-at-a-time mode when usage >= 97%
|
|
1102
|
-
let allModelsSection = `${lt('current_week_all_models', {}, { locale })}\n`;
|
|
1103
|
-
if (hasLimitPercentage(usage?.allModels)) {
|
|
1104
|
-
const timePassed = calculateTimePassedPercentage(usage.allModels.resetsAt, 168);
|
|
1105
|
-
if (timePassed !== null) {
|
|
1106
|
-
const timeBar = getProgressBar(timePassed);
|
|
1107
|
-
allModelsSection += `${timeBar} ${timePassed}% ${lt('passed', {}, { locale })}\n`;
|
|
1108
|
-
}
|
|
1109
1133
|
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
const bar = getProgressBar(pct, DISPLAY_THRESHOLDS.CLAUDE_WEEKLY);
|
|
1114
|
-
const suffix = pct >= DISPLAY_THRESHOLDS.CLAUDE_WEEKLY ? ' ⚠️' : ` ${lt('used', {}, { locale })}`;
|
|
1115
|
-
allModelsSection += `${bar} ${pct}%${suffix}\n`;
|
|
1116
|
-
|
|
1117
|
-
const allModelsResetTime = getLocalizedResetTime(usage.allModels, { locale });
|
|
1118
|
-
if (allModelsResetTime) {
|
|
1119
|
-
const relativeTime = getLocalizedRelativeReset(usage.allModels, { locale });
|
|
1120
|
-
if (relativeTime) {
|
|
1121
|
-
allModelsSection += `${formatLimitResetsIn(relativeTime, allModelsResetTime, { locale })}\n`;
|
|
1122
|
-
} else {
|
|
1123
|
-
allModelsSection += `${formatLimitResetsAt(allModelsResetTime, { locale })}\n`;
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
|
-
} else {
|
|
1127
|
-
allModelsSection += `${lt('na', {}, { locale })}\n`;
|
|
1134
|
+
if (!useShortClaudeLabels) {
|
|
1135
|
+
const subscriptionLines = formatSubscriptionLines(subscription, { locale });
|
|
1136
|
+
if (subscriptionLines) claudeSections.push(subscriptionLines);
|
|
1128
1137
|
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
// Current week (Sonnet only / seven_day_sonnet)
|
|
1132
|
-
// Threshold: One-at-a-time mode when usage >= 97% (same as all models)
|
|
1133
|
-
let sonnetSection = `${lt('current_week_sonnet_only', {}, { locale })}\n`;
|
|
1134
|
-
if (hasLimitPercentage(usage?.sonnetOnly)) {
|
|
1135
|
-
// Add time passed progress bar first (no threshold marker for time)
|
|
1136
|
-
const timePassed = calculateTimePassedPercentage(usage.sonnetOnly.resetsAt, 168);
|
|
1137
|
-
if (timePassed !== null) {
|
|
1138
|
-
const timeBar = getProgressBar(timePassed);
|
|
1139
|
-
sonnetSection += `${timeBar} ${timePassed}% ${lt('passed', {}, { locale })}\n`;
|
|
1140
|
-
}
|
|
1138
|
+
}
|
|
1141
1139
|
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
const sonnetResetTime = getLocalizedResetTime(usage.sonnetOnly, { locale });
|
|
1151
|
-
if (sonnetResetTime) {
|
|
1152
|
-
const relativeTime = getLocalizedRelativeReset(usage.sonnetOnly, { locale });
|
|
1153
|
-
if (relativeTime) {
|
|
1154
|
-
sonnetSection += `${formatLimitResetsIn(relativeTime, sonnetResetTime, { locale })}\n`;
|
|
1155
|
-
} else {
|
|
1156
|
-
sonnetSection += `${formatLimitResetsAt(sonnetResetTime, { locale })}\n`;
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
} else {
|
|
1160
|
-
sonnetSection += `${lt('na', {}, { locale })}\n`;
|
|
1140
|
+
const hasFencedExtraSection = extraSections.some(extra => String(extra ?? '').includes('```'));
|
|
1141
|
+
const useSplitLayout = Boolean(claudeHeading) || hasFencedExtraSection;
|
|
1142
|
+
|
|
1143
|
+
if (!useSplitLayout) {
|
|
1144
|
+
sections.push(...claudeSections);
|
|
1145
|
+
for (const extra of extraSections) {
|
|
1146
|
+
sections.push(extra);
|
|
1161
1147
|
}
|
|
1162
|
-
sections.
|
|
1148
|
+
return formatCodeBlock(sections.join('\n'));
|
|
1149
|
+
}
|
|
1163
1150
|
|
|
1164
|
-
|
|
1165
|
-
|
|
1151
|
+
const markdownSections = [];
|
|
1152
|
+
markdownSections.push(formatCodeBlock(claudeHeading ? sections.join('\n') : [...sections, ...claudeSections].join('\n')));
|
|
1153
|
+
if (claudeHeading) {
|
|
1154
|
+
markdownSections.push(claudeHeading);
|
|
1155
|
+
markdownSections.push(formatCodeBlock(claudeSections.join('\n')));
|
|
1166
1156
|
}
|
|
1167
1157
|
|
|
1168
|
-
// Append any caller-provided extra sections (e.g. queue status) inside the code block
|
|
1169
1158
|
for (const extra of extraSections) {
|
|
1170
|
-
|
|
1159
|
+
const formatted = formatPlainTitledCodeSection(extra);
|
|
1160
|
+
if (formatted) markdownSections.push(formatted);
|
|
1171
1161
|
}
|
|
1172
1162
|
|
|
1173
|
-
|
|
1174
|
-
// Sections are separated by blank lines; the trailing newline on each section provides spacing.
|
|
1175
|
-
return '```\n' + sections.join('\n') + '```';
|
|
1163
|
+
return markdownSections.join('\n\n');
|
|
1176
1164
|
}
|
|
1177
1165
|
|
|
1178
1166
|
/**
|
|
@@ -1185,64 +1173,33 @@ export function formatUsageMessage(usage, diskSpace = null, githubRateLimit = nu
|
|
|
1185
1173
|
*/
|
|
1186
1174
|
export function formatCodexLimitsSection(codexLimits, codexError = null, options = {}) {
|
|
1187
1175
|
const locale = resolveLimitLocale(options);
|
|
1188
|
-
|
|
1189
|
-
return `${lt('codex_limits', {}, { locale })}\n${codexError}\n`;
|
|
1190
|
-
}
|
|
1191
|
-
|
|
1176
|
+
const subscription = options?.subscription || null;
|
|
1192
1177
|
const usage = codexLimits?.usage || null;
|
|
1193
1178
|
const additionalRateLimits = codexLimits?.additionalRateLimits || [];
|
|
1194
1179
|
const credits = codexLimits?.credits || null;
|
|
1195
|
-
const planType = codexLimits?.planType || null;
|
|
1196
|
-
const
|
|
1180
|
+
const planType = subscription?.planType || codexLimits?.planType || null;
|
|
1181
|
+
const heading = formatSubscriptionHeading('codex', subscription, { locale, planType });
|
|
1182
|
+
const useTitledLayout = Boolean(heading);
|
|
1197
1183
|
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1184
|
+
if (codexError) {
|
|
1185
|
+
const errorSection = useTitledLayout ? `${codexError}\n` : `${lt('codex_limits', {}, { locale })}\n${codexError}\n`;
|
|
1186
|
+
return useTitledLayout ? `${heading}\n\n${formatCodeBlock(errorSection)}` : errorSection;
|
|
1201
1187
|
}
|
|
1202
1188
|
|
|
1203
|
-
let
|
|
1204
|
-
if (
|
|
1205
|
-
|
|
1206
|
-
if (timePassed !== null) {
|
|
1207
|
-
sessionSection += `${getProgressBar(timePassed)} ${timePassed}% ${lt('passed', {}, { locale })}\n`;
|
|
1208
|
-
}
|
|
1209
|
-
const pct = Math.floor(usage.currentSession.percentage);
|
|
1210
|
-
const bar = getProgressBar(pct, DISPLAY_THRESHOLDS.CODEX_5_HOUR_SESSION);
|
|
1211
|
-
const suffix = pct >= DISPLAY_THRESHOLDS.CODEX_5_HOUR_SESSION ? ' ⚠️' : ` ${lt('used', {}, { locale })}`;
|
|
1212
|
-
sessionSection += `${bar} ${pct}%${suffix}\n`;
|
|
1213
|
-
const sessionResetTime = getLocalizedResetTime(usage.currentSession, { locale });
|
|
1214
|
-
if (sessionResetTime) {
|
|
1215
|
-
const relativeTime = getLocalizedRelativeReset(usage.currentSession, { locale });
|
|
1216
|
-
sessionSection += relativeTime ? `${formatLimitResetsIn(relativeTime, sessionResetTime, { locale })}\n` : `${formatLimitResetsAt(sessionResetTime, { locale })}\n`;
|
|
1217
|
-
}
|
|
1218
|
-
} else {
|
|
1219
|
-
sessionSection += `${lt('na', {}, { locale })}\n`;
|
|
1189
|
+
let section = useTitledLayout ? '' : `${lt('codex_limits', {}, { locale })}\n`;
|
|
1190
|
+
if (planType && !useTitledLayout) {
|
|
1191
|
+
section += `${lt('plan', {}, { locale })}: ${planType}\n`;
|
|
1220
1192
|
}
|
|
1221
1193
|
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
const timePassed = calculateTimePassedPercentage(usage.allModels.resetsAt, 168);
|
|
1225
|
-
if (timePassed !== null) {
|
|
1226
|
-
weeklySection += `${getProgressBar(timePassed)} ${timePassed}% ${lt('passed', {}, { locale })}\n`;
|
|
1227
|
-
}
|
|
1228
|
-
const pct = Math.floor(usage.allModels.percentage);
|
|
1229
|
-
const bar = getProgressBar(pct, DISPLAY_THRESHOLDS.CODEX_WEEKLY);
|
|
1230
|
-
const suffix = pct >= DISPLAY_THRESHOLDS.CODEX_WEEKLY ? ' ⚠️' : ` ${lt('used', {}, { locale })}`;
|
|
1231
|
-
weeklySection += `${bar} ${pct}%${suffix}\n`;
|
|
1232
|
-
const weeklyResetTime = getLocalizedResetTime(usage.allModels, { locale });
|
|
1233
|
-
if (weeklyResetTime) {
|
|
1234
|
-
const relativeTime = getLocalizedRelativeReset(usage.allModels, { locale });
|
|
1235
|
-
weeklySection += relativeTime ? `${formatLimitResetsIn(relativeTime, weeklyResetTime, { locale })}\n` : `${formatLimitResetsAt(weeklyResetTime, { locale })}\n`;
|
|
1236
|
-
}
|
|
1237
|
-
} else {
|
|
1238
|
-
weeklySection += `${lt('na', {}, { locale })}\n`;
|
|
1239
|
-
}
|
|
1194
|
+
const sessionSection = formatLimitWindowSection(useTitledLayout ? lt('five_hour_limit_session', {}, { locale }) : lt('codex_5_hour_session', {}, { locale }), usage?.currentSession, 5, DISPLAY_THRESHOLDS.CODEX_5_HOUR_SESSION, { locale });
|
|
1195
|
+
const weeklySection = formatLimitWindowSection(useTitledLayout ? lt('current_week', {}, { locale }) : lt('current_week_all_models', {}, { locale }), usage?.allModels, 168, DISPLAY_THRESHOLDS.CODEX_WEEKLY, { locale });
|
|
1240
1196
|
|
|
1241
1197
|
section += `${sessionSection}\n${weeklySection}`;
|
|
1242
1198
|
|
|
1243
|
-
|
|
1199
|
+
const visibleAdditionalRateLimits = additionalRateLimits.filter(limit => hasPositivePercentage(limit.allModels?.percentage));
|
|
1200
|
+
if (visibleAdditionalRateLimits.length > 0) {
|
|
1244
1201
|
section += `\n${lt('additional_codex_limits', {}, { locale })}\n`;
|
|
1245
|
-
for (const limit of
|
|
1202
|
+
for (const limit of visibleAdditionalRateLimits) {
|
|
1246
1203
|
const sessionPct = limit.currentSession?.percentage;
|
|
1247
1204
|
const weeklyPct = limit.allModels?.percentage;
|
|
1248
1205
|
const sessionText = sessionPct === null || sessionPct === undefined ? `${lt('session', {}, { locale })} ${lt('na', {}, { locale })}` : `${lt('session', {}, { locale })} ${Math.floor(sessionPct)}%`;
|
|
@@ -1251,15 +1208,17 @@ export function formatCodexLimitsSection(codexLimits, codexError = null, options
|
|
|
1251
1208
|
}
|
|
1252
1209
|
}
|
|
1253
1210
|
|
|
1254
|
-
if (credits) {
|
|
1211
|
+
if (hasPositiveCreditBalance(credits)) {
|
|
1255
1212
|
const creditSummary = credits.unlimited ? lt('unlimited', {}, { locale }) : `${credits.balance ?? '0'} ${lt('balance', {}, { locale })}`;
|
|
1256
1213
|
section += `\n${lt('codex_credits', {}, { locale })}\n${creditSummary}\n`;
|
|
1257
1214
|
}
|
|
1258
1215
|
|
|
1259
|
-
|
|
1260
|
-
|
|
1216
|
+
if (!useTitledLayout) {
|
|
1217
|
+
const subscriptionLines = formatSubscriptionLines(subscription, { locale });
|
|
1218
|
+
if (subscriptionLines) section += subscriptionLines;
|
|
1219
|
+
}
|
|
1261
1220
|
|
|
1262
|
-
return section;
|
|
1221
|
+
return useTitledLayout ? `${heading}\n\n${formatCodeBlock(section)}` : section;
|
|
1263
1222
|
}
|
|
1264
1223
|
|
|
1265
1224
|
// ============================================================================
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared capability matrix for live issue/PR event input.
|
|
3
|
+
*
|
|
4
|
+
* Issue #2007 asks solve to feed issue/PR events into the running AI tool "in
|
|
5
|
+
* all ways possible", and to provide a universal fallback for every tool that
|
|
6
|
+
* does not have a mid-session live input channel: wait for the current turn to
|
|
7
|
+
* finish in the JSON output, stop the process, and resume the AI session with
|
|
8
|
+
* the new events.
|
|
9
|
+
*
|
|
10
|
+
* Because of that fallback, live event input is *available* for every tool.
|
|
11
|
+
* Tools differ only in the delivery `mode`:
|
|
12
|
+
*
|
|
13
|
+
* - `stream` : the tool exposes a live stdin/JSON channel, so new events are
|
|
14
|
+
* written into the running process without restarting it. Claude
|
|
15
|
+
* and Agent (`--input-format stream-json`) are wired for this today.
|
|
16
|
+
* - `fallback`: no verified mid-session input channel exists yet, so solve uses
|
|
17
|
+
* the restart/resume loop (`--auto-restart-until-mergeable` /
|
|
18
|
+
* `watchUntilMergeable`). It waits for the current session to end,
|
|
19
|
+
* then resumes/restarts the AI with the new issue/PR events as
|
|
20
|
+
* feedback. This works for every tool.
|
|
21
|
+
*
|
|
22
|
+
* Missing native live-input features for each tool are reported upstream in the
|
|
23
|
+
* https://github.com/link-assistant/agent repository so they can be implemented
|
|
24
|
+
* (see `agentIssue`), after which a tool can graduate from `fallback` to `stream`.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
export const ISSUE_2007_REQUIRED_EVENT_IDS = Object.freeze(['issue-title', 'issue-body', 'issue-comments', 'pull-request-comments']);
|
|
28
|
+
|
|
29
|
+
export const LIVE_INPUT_EVENT_SOURCES = Object.freeze([
|
|
30
|
+
Object.freeze({
|
|
31
|
+
id: 'issue-title',
|
|
32
|
+
label: 'Issue title updates',
|
|
33
|
+
requiredByIssue2007: true,
|
|
34
|
+
note: 'User-facing issue metadata should be streamed when it changes during a run.',
|
|
35
|
+
}),
|
|
36
|
+
Object.freeze({
|
|
37
|
+
id: 'issue-body',
|
|
38
|
+
label: 'Issue description updates',
|
|
39
|
+
requiredByIssue2007: true,
|
|
40
|
+
note: 'The issue body is treated as user feedback because it can change after the agent starts.',
|
|
41
|
+
}),
|
|
42
|
+
Object.freeze({
|
|
43
|
+
id: 'issue-comments',
|
|
44
|
+
label: 'Issue comments',
|
|
45
|
+
requiredByIssue2007: true,
|
|
46
|
+
note: 'New non-system issue comments are user feedback.',
|
|
47
|
+
}),
|
|
48
|
+
Object.freeze({
|
|
49
|
+
id: 'pull-request-comments',
|
|
50
|
+
label: 'Pull request comments',
|
|
51
|
+
requiredByIssue2007: true,
|
|
52
|
+
note: 'New non-system PR conversation comments and review comments should reach the agent.',
|
|
53
|
+
}),
|
|
54
|
+
Object.freeze({
|
|
55
|
+
id: 'pull-request-description',
|
|
56
|
+
label: 'Pull request description updates',
|
|
57
|
+
requiredByIssue2007: false,
|
|
58
|
+
note: 'Issue #2007 explicitly treats the PR description as AI-owned, so it is not a required user-feedback source.',
|
|
59
|
+
}),
|
|
60
|
+
]);
|
|
61
|
+
|
|
62
|
+
const REQUIRED_EVENTS = ISSUE_2007_REQUIRED_EVENT_IDS;
|
|
63
|
+
|
|
64
|
+
// Delivery modes for live issue/PR event input.
|
|
65
|
+
export const LIVE_INPUT_MODE_STREAM = 'stream';
|
|
66
|
+
export const LIVE_INPUT_MODE_FALLBACK = 'fallback';
|
|
67
|
+
|
|
68
|
+
// Shared description of the universal restart/resume fallback so every tool
|
|
69
|
+
// entry reports it identically.
|
|
70
|
+
const FALLBACK_DESCRIPTION = 'Universal fallback: wait for the current AI turn to finish in the JSON output, stop the process, then resume/restart the AI session with the new issue/PR events as feedback via --auto-restart-until-mergeable (watchUntilMergeable). Works for every tool even without a live stdin channel.';
|
|
71
|
+
|
|
72
|
+
const CAPABILITIES = Object.freeze({
|
|
73
|
+
claude: Object.freeze({
|
|
74
|
+
tool: 'claude',
|
|
75
|
+
label: 'Claude',
|
|
76
|
+
available: true,
|
|
77
|
+
mode: LIVE_INPUT_MODE_STREAM,
|
|
78
|
+
liveStreaming: true,
|
|
79
|
+
supported: true,
|
|
80
|
+
option: '--auto-input-until-mergeable',
|
|
81
|
+
protocol: 'claude --input-format stream-json stdin NDJSON',
|
|
82
|
+
currentRunner: 'src/claude.lib.mjs keeps stdin as a pipe and attaches bidirectional-interactive.lib.mjs',
|
|
83
|
+
futureProtocol: '',
|
|
84
|
+
fallback: FALLBACK_DESCRIPTION,
|
|
85
|
+
events: REQUIRED_EVENTS,
|
|
86
|
+
agentIssue: '',
|
|
87
|
+
unsupportedReason: '',
|
|
88
|
+
testing: 'Run solve with --tool claude --auto-input-until-mergeable, add an issue or PR comment while the Claude process is alive, and watch for the bidirectional handler to queue or stream a user frame into stdin.',
|
|
89
|
+
}),
|
|
90
|
+
codex: Object.freeze({
|
|
91
|
+
tool: 'codex',
|
|
92
|
+
label: 'Codex',
|
|
93
|
+
available: true,
|
|
94
|
+
mode: LIVE_INPUT_MODE_FALLBACK,
|
|
95
|
+
liveStreaming: false,
|
|
96
|
+
supported: false,
|
|
97
|
+
option: '--auto-input-until-mergeable',
|
|
98
|
+
protocol: 'Restart/resume fallback (no live stdin wired through solve for Codex yet).',
|
|
99
|
+
currentRunner: 'src/codex.lib.mjs uses codex exec with prompt/stdin context at process start',
|
|
100
|
+
futureProtocol: 'Codex app-server JSON-RPC turn/steer',
|
|
101
|
+
fallback: FALLBACK_DESCRIPTION,
|
|
102
|
+
events: REQUIRED_EVENTS,
|
|
103
|
+
agentIssue: 'https://github.com/link-assistant/agent/issues',
|
|
104
|
+
unsupportedReason: 'The current solve Codex runner uses codex exec, whose stdin is one-shot prompt/context at process start. It does not expose a live JSON input pipe for mid-session issue/PR events, so the restart/resume fallback is used. Codex app-server turn/steer is the candidate protocol for a future live-streaming Codex runner.',
|
|
105
|
+
testing: 'Passing --tool codex --auto-input-until-mergeable activates the restart/resume fallback: the run finishes the current session, then resumes with the new issue/PR events.',
|
|
106
|
+
}),
|
|
107
|
+
agent: Object.freeze({
|
|
108
|
+
tool: 'agent',
|
|
109
|
+
label: 'Agent',
|
|
110
|
+
available: true,
|
|
111
|
+
mode: LIVE_INPUT_MODE_STREAM,
|
|
112
|
+
liveStreaming: true,
|
|
113
|
+
supported: true,
|
|
114
|
+
option: '--auto-input-until-mergeable',
|
|
115
|
+
protocol: 'agent --input-format stream-json --output-format stream-json stdin/stdout NDJSON',
|
|
116
|
+
currentRunner: 'src/agent.lib.mjs keeps stdin as a pipe and attaches bidirectional-interactive.lib.mjs when live input is enabled',
|
|
117
|
+
futureProtocol: '',
|
|
118
|
+
fallback: FALLBACK_DESCRIPTION,
|
|
119
|
+
events: REQUIRED_EVENTS,
|
|
120
|
+
agentIssue: 'https://github.com/link-assistant/agent/pull/274',
|
|
121
|
+
unsupportedReason: '',
|
|
122
|
+
testing: 'Run solve with --tool agent --auto-input-until-mergeable, add an issue or PR comment while the Agent process is alive, and watch for the bidirectional handler to queue or stream a user frame into stdin.',
|
|
123
|
+
}),
|
|
124
|
+
opencode: Object.freeze({
|
|
125
|
+
tool: 'opencode',
|
|
126
|
+
label: 'OpenCode',
|
|
127
|
+
available: true,
|
|
128
|
+
mode: LIVE_INPUT_MODE_FALLBACK,
|
|
129
|
+
liveStreaming: false,
|
|
130
|
+
supported: false,
|
|
131
|
+
option: '--auto-input-until-mergeable',
|
|
132
|
+
protocol: 'Restart/resume fallback (no live JSON input channel wired through solve for OpenCode yet).',
|
|
133
|
+
currentRunner: 'src/opencode.lib.mjs uses a prompt-via-file/stdin pattern',
|
|
134
|
+
futureProtocol: '',
|
|
135
|
+
fallback: FALLBACK_DESCRIPTION,
|
|
136
|
+
events: REQUIRED_EVENTS,
|
|
137
|
+
agentIssue: 'https://github.com/link-assistant/agent/issues',
|
|
138
|
+
unsupportedReason: 'No verified live JSON input channel is wired through solve for OpenCode yet, so the restart/resume fallback is used.',
|
|
139
|
+
testing: 'Passing --tool opencode --auto-input-until-mergeable activates the restart/resume fallback.',
|
|
140
|
+
}),
|
|
141
|
+
gemini: Object.freeze({
|
|
142
|
+
tool: 'gemini',
|
|
143
|
+
label: 'Gemini',
|
|
144
|
+
available: true,
|
|
145
|
+
mode: LIVE_INPUT_MODE_FALLBACK,
|
|
146
|
+
liveStreaming: false,
|
|
147
|
+
supported: false,
|
|
148
|
+
option: '--auto-input-until-mergeable',
|
|
149
|
+
protocol: 'Restart/resume fallback (no live JSON input channel wired through solve for Gemini yet).',
|
|
150
|
+
currentRunner: 'src/gemini.lib.mjs uses a prompt-driven process invocation',
|
|
151
|
+
futureProtocol: '',
|
|
152
|
+
fallback: FALLBACK_DESCRIPTION,
|
|
153
|
+
events: REQUIRED_EVENTS,
|
|
154
|
+
agentIssue: 'https://github.com/link-assistant/agent/issues',
|
|
155
|
+
unsupportedReason: 'No verified live JSON input channel is wired through solve for Gemini yet, so the restart/resume fallback is used.',
|
|
156
|
+
testing: 'Passing --tool gemini --auto-input-until-mergeable activates the restart/resume fallback.',
|
|
157
|
+
}),
|
|
158
|
+
qwen: Object.freeze({
|
|
159
|
+
tool: 'qwen',
|
|
160
|
+
label: 'Qwen',
|
|
161
|
+
available: true,
|
|
162
|
+
mode: LIVE_INPUT_MODE_FALLBACK,
|
|
163
|
+
liveStreaming: false,
|
|
164
|
+
supported: false,
|
|
165
|
+
option: '--auto-input-until-mergeable',
|
|
166
|
+
protocol: 'Restart/resume fallback (no live JSON input channel wired through solve for Qwen yet).',
|
|
167
|
+
currentRunner: 'src/qwen.lib.mjs uses a prompt-driven process invocation',
|
|
168
|
+
futureProtocol: '',
|
|
169
|
+
fallback: FALLBACK_DESCRIPTION,
|
|
170
|
+
events: REQUIRED_EVENTS,
|
|
171
|
+
agentIssue: 'https://github.com/link-assistant/agent/issues',
|
|
172
|
+
unsupportedReason: 'No verified live JSON input channel is wired through solve for Qwen yet, so the restart/resume fallback is used.',
|
|
173
|
+
testing: 'Passing --tool qwen --auto-input-until-mergeable activates the restart/resume fallback.',
|
|
174
|
+
}),
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const UNKNOWN_CAPABILITY = tool =>
|
|
178
|
+
Object.freeze({
|
|
179
|
+
tool,
|
|
180
|
+
label: tool,
|
|
181
|
+
available: true,
|
|
182
|
+
mode: LIVE_INPUT_MODE_FALLBACK,
|
|
183
|
+
liveStreaming: false,
|
|
184
|
+
supported: false,
|
|
185
|
+
option: '--auto-input-until-mergeable',
|
|
186
|
+
protocol: 'Restart/resume fallback (no live JSON input channel wired through solve for this tool yet).',
|
|
187
|
+
currentRunner: 'Unknown or custom solve tool runner',
|
|
188
|
+
futureProtocol: '',
|
|
189
|
+
fallback: FALLBACK_DESCRIPTION,
|
|
190
|
+
events: REQUIRED_EVENTS,
|
|
191
|
+
agentIssue: 'https://github.com/link-assistant/agent/issues',
|
|
192
|
+
unsupportedReason: `No verified live JSON input channel is wired through solve for ${tool}, so the restart/resume fallback is used. Add a live-input capability entry (and report the missing native API to link-assistant/agent) once the runner has a long-lived stdin, JSON-RPC, or SDK channel that accepts new user turns mid-session.`,
|
|
193
|
+
testing: 'The flag activates the restart/resume fallback until a live-streaming runner is implemented.',
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
export const getLiveInputCapability = tool => {
|
|
197
|
+
const normalizedTool = String(tool || '')
|
|
198
|
+
.trim()
|
|
199
|
+
.toLowerCase();
|
|
200
|
+
return CAPABILITIES[normalizedTool] || UNKNOWN_CAPABILITY(normalizedTool || 'unknown');
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Whether the tool has a live *streaming* input channel (writes events into the
|
|
205
|
+
* running process). Kept as `isLiveInputSupported` for backward compatibility;
|
|
206
|
+
* it is the stream-mode predicate, not "is live input available at all".
|
|
207
|
+
*/
|
|
208
|
+
export const isLiveInputSupported = tool => getLiveInputCapability(tool).mode === LIVE_INPUT_MODE_STREAM;
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Whether live issue/PR event input is available for the tool in *any* mode
|
|
212
|
+
* (streaming or restart/resume fallback). This is true for every tool.
|
|
213
|
+
*/
|
|
214
|
+
export const isLiveInputAvailable = tool => getLiveInputCapability(tool).available === true;
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Resolve the delivery mode ('stream' or 'fallback') for a tool.
|
|
218
|
+
*/
|
|
219
|
+
export const getLiveInputMode = tool => getLiveInputCapability(tool).mode;
|
|
220
|
+
|
|
221
|
+
export const getLiveInputCapabilityRows = () => Object.values(CAPABILITIES);
|
package/src/locales/en.lino
CHANGED
|
@@ -192,6 +192,11 @@ en
|
|
|
192
192
|
hour
|
|
193
193
|
session "Claude 5 hour session"
|
|
194
194
|
limits "Claude limits"
|
|
195
|
+
subscription
|
|
196
|
+
title "Claude{{plan}} subscription"
|
|
197
|
+
chatgpt
|
|
198
|
+
subscription
|
|
199
|
+
title "ChatGPT{{plan}} subscription"
|
|
195
200
|
codex
|
|
196
201
|
5
|
|
197
202
|
hour
|
|
@@ -205,6 +210,7 @@ en
|
|
|
205
210
|
current
|
|
206
211
|
time "Current time"
|
|
207
212
|
week
|
|
213
|
+
label "Current week"
|
|
208
214
|
all
|
|
209
215
|
models "Current week (all models)"
|
|
210
216
|
sonnet
|
|
@@ -226,6 +232,8 @@ en
|
|
|
226
232
|
end "End"
|
|
227
233
|
five
|
|
228
234
|
hour
|
|
235
|
+
limit
|
|
236
|
+
session "5 hour session"
|
|
229
237
|
session "5h session"
|
|
230
238
|
min
|
|
231
239
|
load
|
|
@@ -314,6 +322,14 @@ en
|
|
|
314
322
|
session "session"
|
|
315
323
|
start "Start"
|
|
316
324
|
subscription
|
|
325
|
+
detail
|
|
326
|
+
ends
|
|
327
|
+
label "ends {{time}}"
|
|
328
|
+
in "ends in {{duration}}; {{time}}"
|
|
329
|
+
trial
|
|
330
|
+
ends
|
|
331
|
+
label "trial ends {{time}}"
|
|
332
|
+
in "trial ends in {{duration}}; {{time}}"
|
|
317
333
|
ends
|
|
318
334
|
label "Subscription ends {{time}}"
|
|
319
335
|
in "Subscription ends in {{duration}} ({{time}})"
|
package/src/locales/hi.lino
CHANGED
|
@@ -192,6 +192,11 @@ hi
|
|
|
192
192
|
hour
|
|
193
193
|
session "Claude 5 घंटे का सत्र"
|
|
194
194
|
limits "Claude सीमाएँ"
|
|
195
|
+
subscription
|
|
196
|
+
title "Claude{{plan}} सदस्यता"
|
|
197
|
+
chatgpt
|
|
198
|
+
subscription
|
|
199
|
+
title "ChatGPT{{plan}} सदस्यता"
|
|
195
200
|
codex
|
|
196
201
|
5
|
|
197
202
|
hour
|
|
@@ -205,6 +210,7 @@ hi
|
|
|
205
210
|
current
|
|
206
211
|
time "वर्तमान समय"
|
|
207
212
|
week
|
|
213
|
+
label "वर्तमान सप्ताह"
|
|
208
214
|
all
|
|
209
215
|
models "वर्तमान सप्ताह (सभी मॉडल)"
|
|
210
216
|
sonnet
|
|
@@ -226,6 +232,8 @@ hi
|
|
|
226
232
|
end "समाप्ति"
|
|
227
233
|
five
|
|
228
234
|
hour
|
|
235
|
+
limit
|
|
236
|
+
session "5 घंटे का सत्र"
|
|
229
237
|
session "5 घंटे का सत्र"
|
|
230
238
|
min
|
|
231
239
|
load
|
|
@@ -314,6 +322,14 @@ hi
|
|
|
314
322
|
session "सत्र"
|
|
315
323
|
start "शुरुआत"
|
|
316
324
|
subscription
|
|
325
|
+
detail
|
|
326
|
+
ends
|
|
327
|
+
label "{{time}} को समाप्त होगी"
|
|
328
|
+
in "{{duration}} में समाप्त होगी; {{time}}"
|
|
329
|
+
trial
|
|
330
|
+
ends
|
|
331
|
+
label "ट्रायल {{time}} को समाप्त होगा"
|
|
332
|
+
in "ट्रायल {{duration}} में समाप्त होगा; {{time}}"
|
|
317
333
|
ends
|
|
318
334
|
label "सदस्यता समाप्त होगी {{time}}"
|
|
319
335
|
in "सदस्यता {{duration}} में समाप्त होगी ({{time}})"
|