@pasko70/pibo 2.2.4 → 2.2.5
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/dist/agent-runtime/capabilities.js +9 -1
- package/dist/agent-runtimes/codex-native/adapter.js +5 -0
- package/dist/agent-runtimes/omp/adapter.js +5 -0
- package/dist/agent-runtimes/omp/turn.js +2 -0
- package/dist/agent-runtimes/pi/adapter.js +27 -6
- package/dist/agent-runtimes/pi/intent-tracing.js +120 -0
- package/dist/agent-runtimes/pi/routed-session.js +25 -8
- package/dist/agent-runtimes/pi/runtime.js +3 -0
- package/dist/apps/chat/chat-settings-routes.js +44 -3
- package/dist/apps/chat/chat-transcription.js +85 -0
- package/dist/apps/chat/data/chat-data-mappers.js +4 -4
- package/dist/apps/chat/stream.js +23 -9
- package/dist/apps/chat/trace-v2.js +1 -0
- package/dist/apps/chat/web-app.js +49 -0
- package/dist/apps/chat-ui/assets/{dist-CImWCF2M.js → dist-BCRR_a0Z.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-luJVFsmq.js → dist-BK7eaoLT.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-BFwTuvyi.js → dist-CTUT484B.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-ltfz1vFX.js → dist-fNgdnmyE.js} +1 -1
- package/dist/apps/chat-ui/assets/{dist-rf4HYdoa.js → dist-wq3P0LZj.js} +1 -1
- package/dist/apps/chat-ui/assets/{index-nZQXUpxE.css → index-BJ56TREg.css} +1 -1
- package/dist/apps/chat-ui/assets/index-DsFKL_EZ.js +228 -0
- package/dist/apps/chat-ui/index.html +2 -2
- package/dist/apps/chat-vscode-web/assets/index-R4FTxr78.js +43 -0
- package/dist/apps/chat-vscode-web/index.html +1 -1
- package/dist/apps/vscode-artifacts/latest.vsix +0 -0
- package/dist/apps/vscode-artifacts/pibo-vscode-ext-2.2.5.vsix +0 -0
- package/dist/core/gateway-resource-guard.js +35 -14
- package/dist/core/gateway-settings.js +64 -0
- package/dist/core/session-router.js +5 -1
- package/dist/core/user-settings.js +20 -0
- package/dist/data/ingest-service.js +2 -2
- package/dist/debug/index.js +7 -2
- package/dist/gateway/server.js +2 -0
- package/dist/index.js +5 -0
- package/dist/plugins/builtin.js +4 -2
- package/dist/plugins/openai-chatgpt-transcription.js +12 -0
- package/dist/plugins/openai-transcription.js +12 -0
- package/dist/plugins/registry.js +23 -0
- package/dist/session-ui/terminalRows.js +51 -1
- package/dist/shared/trace-event-projection.js +4 -0
- package/dist/shared/trace-live-reducer.js +4 -0
- package/dist/shared/trace-patch-nodes.js +1 -0
- package/dist/transcription/openai-chatgpt.js +148 -0
- package/dist/transcription/openai.js +72 -0
- package/dist/transcription/types.js +8 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/dist/apps/chat-ui/assets/index-D2Maa2v1.js +0 -226
- package/dist/apps/chat-vscode-web/assets/index-BpurWVnX.js +0 -41
- package/dist/apps/vscode-artifacts/pibo-vscode-ext-2.2.4.vsix +0 -0
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<meta name="theme-color" content="#101d22" />
|
|
7
7
|
<title>Pibo</title>
|
|
8
|
-
<script type="module" crossorigin src="/apps/chat-vscode/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/apps/chat-vscode/assets/index-R4FTxr78.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/apps/chat-vscode/assets/index-CF1-9PKU.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
Binary file
|
|
Binary file
|
|
@@ -3,6 +3,7 @@ import { freemem, totalmem } from "node:os";
|
|
|
3
3
|
import { promisify } from "node:util";
|
|
4
4
|
import { getHeapStatistics } from "node:v8";
|
|
5
5
|
import { collectYieldedRunHostResourceSnapshot } from "../runs/resource-isolation.js";
|
|
6
|
+
import { DEFAULT_GATEWAY_CONCURRENT_YIELDED_RUNS, DEFAULT_SESSION_CONCURRENT_YIELDED_RUNS, resolvePiboGatewaySettings, } from "./gateway-settings.js";
|
|
6
7
|
const execFileAsync = promisify(execFile);
|
|
7
8
|
const DEFAULT_POLICY = Object.freeze({
|
|
8
9
|
mode: "block",
|
|
@@ -10,17 +11,19 @@ const DEFAULT_POLICY = Object.freeze({
|
|
|
10
11
|
minHeapAvailableBytes: 64 * 1024 * 1024,
|
|
11
12
|
maxRssBytes: 1536 * 1024 * 1024,
|
|
12
13
|
knownDaemonWarningRssBytes: 2 * 1024 * 1024 * 1024,
|
|
13
|
-
maxConcurrentYieldedRuns:
|
|
14
|
+
maxConcurrentYieldedRuns: DEFAULT_GATEWAY_CONCURRENT_YIELDED_RUNS,
|
|
15
|
+
sessionConcurrentYieldedRuns: DEFAULT_SESSION_CONCURRENT_YIELDED_RUNS,
|
|
14
16
|
yieldedRunMemoryReservationBytes: 2 * 1024 * 1024 * 1024,
|
|
15
17
|
});
|
|
16
|
-
export function resolveGatewayResourceGuardPolicy(env = process.env) {
|
|
18
|
+
export function resolveGatewayResourceGuardPolicy(env = process.env, gatewaySettings = resolvePiboGatewaySettings(env)) {
|
|
17
19
|
return {
|
|
18
20
|
mode: parseMode(env.PIBO_GATEWAY_RESOURCE_GUARD, DEFAULT_POLICY.mode),
|
|
19
21
|
minFreeMemoryBytes: parseByteThreshold(env.PIBO_GATEWAY_MIN_FREE_MEMORY_BYTES, DEFAULT_POLICY.minFreeMemoryBytes),
|
|
20
22
|
minHeapAvailableBytes: parseByteThreshold(env.PIBO_GATEWAY_MIN_HEAP_AVAILABLE_BYTES, DEFAULT_POLICY.minHeapAvailableBytes),
|
|
21
23
|
maxRssBytes: parseByteThreshold(env.PIBO_GATEWAY_MAX_RSS_BYTES, DEFAULT_POLICY.maxRssBytes),
|
|
22
24
|
knownDaemonWarningRssBytes: parseByteThreshold(env.PIBO_GATEWAY_KNOWN_DAEMON_WARNING_RSS_BYTES, DEFAULT_POLICY.knownDaemonWarningRssBytes),
|
|
23
|
-
maxConcurrentYieldedRuns:
|
|
25
|
+
maxConcurrentYieldedRuns: gatewaySettings.maxConcurrentYieldedRuns,
|
|
26
|
+
sessionConcurrentYieldedRuns: gatewaySettings.sessionConcurrentYieldedRuns,
|
|
24
27
|
yieldedRunMemoryReservationBytes: parseByteThreshold(env.PIBO_GATEWAY_YIELDED_RUN_MEMORY_RESERVATION_BYTES, DEFAULT_POLICY.yieldedRunMemoryReservationBytes),
|
|
25
28
|
};
|
|
26
29
|
}
|
|
@@ -39,7 +42,7 @@ export function collectGatewayProcessMemory() {
|
|
|
39
42
|
};
|
|
40
43
|
}
|
|
41
44
|
export function buildGatewayResourceSnapshot(options = {}) {
|
|
42
|
-
const policy = resolveGatewayResourceGuardPolicy(options.env);
|
|
45
|
+
const policy = resolveGatewayResourceGuardPolicy(options.env, options.gatewaySettings);
|
|
43
46
|
const gateway = collectGatewayProcessMemory();
|
|
44
47
|
const hostResources = options.hostResourceSnapshot ?? collectYieldedRunHostResourceSnapshot({ now: options.now });
|
|
45
48
|
const host = { freeBytes: hostResources.memoryFreeBytes || freemem(), availableBytes: hostResources.memoryAvailableBytes || freemem(), totalBytes: totalmem() };
|
|
@@ -91,15 +94,30 @@ export function assertGatewayResourceAvailableForWork(workLabel, env = process.e
|
|
|
91
94
|
}
|
|
92
95
|
export class GatewayWorkAdmissionController {
|
|
93
96
|
activeReservations = new Set();
|
|
94
|
-
|
|
95
|
-
|
|
97
|
+
activeSessionReservations = new Map();
|
|
98
|
+
reserve(workLabel, options = {}) {
|
|
99
|
+
const snapshot = buildGatewayResourceSnapshot({
|
|
100
|
+
env: options.env,
|
|
101
|
+
gatewaySettings: options.gatewaySettings,
|
|
102
|
+
includeProcesses: false,
|
|
103
|
+
});
|
|
96
104
|
const policy = snapshot.policy;
|
|
97
105
|
if (snapshot.guardAction === "block") {
|
|
98
106
|
throwGatewayResourceBlock(workLabel, snapshot.checks.filter((check) => check.severity === "critical").map((check) => check.message));
|
|
99
107
|
}
|
|
100
108
|
if (policy.mode === "block" && this.activeReservations.size >= policy.maxConcurrentYieldedRuns) {
|
|
101
109
|
throwGatewayResourceBlock(workLabel, [
|
|
102
|
-
`Active yielded runs ${this.activeReservations.size} reached the configured limit ${policy.maxConcurrentYieldedRuns}. Wait for an active run to settle or raise PIBO_GATEWAY_MAX_CONCURRENT_YIELDED_RUNS
|
|
110
|
+
`Active yielded runs ${this.activeReservations.size} reached the configured gateway limit ${policy.maxConcurrentYieldedRuns}. Wait for an active run to settle or raise PIBO_GATEWAY_MAX_CONCURRENT_YIELDED_RUNS in Settings or the gateway environment.`,
|
|
111
|
+
]);
|
|
112
|
+
}
|
|
113
|
+
const activeForSession = options.sessionId
|
|
114
|
+
? this.activeSessionReservations.get(options.sessionId) ?? 0
|
|
115
|
+
: 0;
|
|
116
|
+
if (policy.mode === "block"
|
|
117
|
+
&& options.sessionId
|
|
118
|
+
&& activeForSession >= policy.sessionConcurrentYieldedRuns) {
|
|
119
|
+
throwGatewayResourceBlock(workLabel, [
|
|
120
|
+
`Session ${options.sessionId} has ${activeForSession} active yielded runs and reached the configured session limit ${policy.sessionConcurrentYieldedRuns}. Wait for an active run to settle or raise PIBO_SESSION_CONCURRENT_YIELDED_RUNS in Settings or the gateway environment.`,
|
|
103
121
|
]);
|
|
104
122
|
}
|
|
105
123
|
if (policy.mode === "block" &&
|
|
@@ -110,6 +128,8 @@ export class GatewayWorkAdmissionController {
|
|
|
110
128
|
}
|
|
111
129
|
const reservation = Symbol(workLabel);
|
|
112
130
|
this.activeReservations.add(reservation);
|
|
131
|
+
if (options.sessionId)
|
|
132
|
+
this.activeSessionReservations.set(options.sessionId, activeForSession + 1);
|
|
113
133
|
let released = false;
|
|
114
134
|
return {
|
|
115
135
|
admission: collectYieldedRunHostResourceSnapshot(),
|
|
@@ -118,6 +138,13 @@ export class GatewayWorkAdmissionController {
|
|
|
118
138
|
return;
|
|
119
139
|
released = true;
|
|
120
140
|
this.activeReservations.delete(reservation);
|
|
141
|
+
if (!options.sessionId)
|
|
142
|
+
return;
|
|
143
|
+
const nextSessionCount = (this.activeSessionReservations.get(options.sessionId) ?? 1) - 1;
|
|
144
|
+
if (nextSessionCount > 0)
|
|
145
|
+
this.activeSessionReservations.set(options.sessionId, nextSessionCount);
|
|
146
|
+
else
|
|
147
|
+
this.activeSessionReservations.delete(options.sessionId);
|
|
121
148
|
},
|
|
122
149
|
};
|
|
123
150
|
}
|
|
@@ -157,7 +184,7 @@ export function renderGatewayResourceSnapshotText(snapshot) {
|
|
|
157
184
|
lines.push(`Gateway PID: ${snapshot.gateway.pid}`);
|
|
158
185
|
lines.push(`Gateway memory: rss=${snapshot.gateway.rssBytes} heapUsed=${snapshot.gateway.heapUsedBytes} heapAvailable=${snapshot.gateway.heapAvailableBytes} heapLimit=${snapshot.gateway.heapLimitBytes}`);
|
|
159
186
|
lines.push(`Host memory: free=${snapshot.host.freeBytes} available=${snapshot.host.availableBytes} total=${snapshot.host.totalBytes}`);
|
|
160
|
-
lines.push(`Thresholds: minFree=${snapshot.policy.minFreeMemoryBytes} minHeapAvailable=${snapshot.policy.minHeapAvailableBytes} maxRss=${snapshot.policy.maxRssBytes} daemonWarnRss=${snapshot.policy.knownDaemonWarningRssBytes} maxYieldedRuns=${snapshot.policy.maxConcurrentYieldedRuns} yieldedRunReservation=${snapshot.policy.yieldedRunMemoryReservationBytes}`);
|
|
187
|
+
lines.push(`Thresholds: minFree=${snapshot.policy.minFreeMemoryBytes} minHeapAvailable=${snapshot.policy.minHeapAvailableBytes} maxRss=${snapshot.policy.maxRssBytes} daemonWarnRss=${snapshot.policy.knownDaemonWarningRssBytes} maxYieldedRuns=${snapshot.policy.maxConcurrentYieldedRuns} sessionYieldedRuns=${snapshot.policy.sessionConcurrentYieldedRuns} yieldedRunReservation=${snapshot.policy.yieldedRunMemoryReservationBytes}`);
|
|
161
188
|
lines.push(`Related processes: children=${snapshot.processes.children.length} knownDaemons=${snapshot.processes.knownDaemons.length} processList=${snapshot.processes.available ? "available" : "unavailable"}`);
|
|
162
189
|
lines.push(`Yielded-run cgroups: units=${snapshot.yieldedRunUnits.units.length} systemdList=${snapshot.yieldedRunUnits.available ? "available" : "unavailable"}`);
|
|
163
190
|
if (snapshot.processes.error)
|
|
@@ -242,12 +269,6 @@ function parseByteThreshold(value, fallback) {
|
|
|
242
269
|
const parsed = Number(value);
|
|
243
270
|
return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : fallback;
|
|
244
271
|
}
|
|
245
|
-
function parsePositiveInteger(value, fallback) {
|
|
246
|
-
if (value === undefined || value.trim() === "")
|
|
247
|
-
return fallback;
|
|
248
|
-
const parsed = Number(value);
|
|
249
|
-
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
250
|
-
}
|
|
251
272
|
function throwGatewayResourceBlock(workLabel, reasons) {
|
|
252
273
|
throw new Error(`Gateway resource guard blocked ${workLabel} before starting: ${reasons.join("; ")}`);
|
|
253
274
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { piboHomePath } from "./pibo-home.js";
|
|
4
|
+
export const DEFAULT_GATEWAY_CONCURRENT_YIELDED_RUNS = 50;
|
|
5
|
+
export const DEFAULT_SESSION_CONCURRENT_YIELDED_RUNS = 10;
|
|
6
|
+
export const DEFAULT_PIBO_GATEWAY_SETTINGS_PATH = "gateway-settings.json";
|
|
7
|
+
export function loadPiboGatewaySettings(env = process.env) {
|
|
8
|
+
return sanitizePiboGatewaySettings(readState().settings, env);
|
|
9
|
+
}
|
|
10
|
+
export function updatePiboGatewaySettings(patch, env = process.env) {
|
|
11
|
+
const state = readState();
|
|
12
|
+
const current = sanitizePiboGatewaySettings(state.settings, env);
|
|
13
|
+
const next = sanitizePiboGatewaySettings({ ...current, ...patch }, env);
|
|
14
|
+
state.settings = next;
|
|
15
|
+
writeState(state);
|
|
16
|
+
return next;
|
|
17
|
+
}
|
|
18
|
+
export function resolvePiboGatewaySettings(env = process.env) {
|
|
19
|
+
return {
|
|
20
|
+
maxConcurrentYieldedRuns: parsePositiveInteger(env.PIBO_GATEWAY_MAX_CONCURRENT_YIELDED_RUNS, DEFAULT_GATEWAY_CONCURRENT_YIELDED_RUNS),
|
|
21
|
+
sessionConcurrentYieldedRuns: parsePositiveInteger(env.PIBO_SESSION_CONCURRENT_YIELDED_RUNS, DEFAULT_SESSION_CONCURRENT_YIELDED_RUNS),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export function sanitizePiboGatewaySettings(value, env = process.env) {
|
|
25
|
+
const raw = value && typeof value === "object" && !Array.isArray(value)
|
|
26
|
+
? value
|
|
27
|
+
: {};
|
|
28
|
+
const defaults = resolvePiboGatewaySettings(env);
|
|
29
|
+
return {
|
|
30
|
+
maxConcurrentYieldedRuns: sanitizeConcurrentYieldedRuns(raw.maxConcurrentYieldedRuns)
|
|
31
|
+
?? defaults.maxConcurrentYieldedRuns,
|
|
32
|
+
sessionConcurrentYieldedRuns: sanitizeConcurrentYieldedRuns(raw.sessionConcurrentYieldedRuns)
|
|
33
|
+
?? defaults.sessionConcurrentYieldedRuns,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export function sanitizeConcurrentYieldedRuns(value) {
|
|
37
|
+
if (typeof value !== "number" && typeof value !== "string")
|
|
38
|
+
return undefined;
|
|
39
|
+
const parsed = typeof value === "number" ? value : Number(value.trim());
|
|
40
|
+
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;
|
|
41
|
+
}
|
|
42
|
+
function parsePositiveInteger(value, fallback) {
|
|
43
|
+
return sanitizeConcurrentYieldedRuns(value) ?? fallback;
|
|
44
|
+
}
|
|
45
|
+
function readState() {
|
|
46
|
+
const path = piboHomePath(DEFAULT_PIBO_GATEWAY_SETTINGS_PATH);
|
|
47
|
+
if (!existsSync(path))
|
|
48
|
+
return {};
|
|
49
|
+
try {
|
|
50
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
51
|
+
const raw = parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
52
|
+
? parsed
|
|
53
|
+
: {};
|
|
54
|
+
return { settings: raw.settings };
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function writeState(state) {
|
|
61
|
+
const path = piboHomePath(DEFAULT_PIBO_GATEWAY_SETTINGS_PATH);
|
|
62
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
63
|
+
writeFileSync(path, `${JSON.stringify(state, null, 2)}\n`);
|
|
64
|
+
}
|
|
@@ -15,6 +15,7 @@ import { AgentRuntimeBindingMissingError, AgentRuntimeUnavailableError } from ".
|
|
|
15
15
|
import { validateAgentRuntimeProfileCapabilities } from "../agent-runtime/profile-validation.js";
|
|
16
16
|
import { getDefaultPiboWorkspace } from "./workspace.js";
|
|
17
17
|
import { loadPiboModelDefaults, selectRequestedFastMode } from "./model-defaults.js";
|
|
18
|
+
import { loadPiboGatewaySettings } from "./gateway-settings.js";
|
|
18
19
|
import { loadPiboUserSettings } from "./user-settings.js";
|
|
19
20
|
import { resolvePiboSessionActiveModel } from "./session-model.js";
|
|
20
21
|
import { isPiboThinkingLevel } from "./thinking.js";
|
|
@@ -1630,7 +1631,10 @@ export class PiboSessionRouter {
|
|
|
1630
1631
|
createRunToolController(parentPiboSessionId) {
|
|
1631
1632
|
return {
|
|
1632
1633
|
startToolRun: ({ toolName, params, completionPolicy, retryable, maxAttempts, timeoutMs, serviceWarning, resources, execute, cancel }) => {
|
|
1633
|
-
const admission = this.gatewayWorkAdmission.reserve(`yielded run ${toolName}
|
|
1634
|
+
const admission = this.gatewayWorkAdmission.reserve(`yielded run ${toolName}`, {
|
|
1635
|
+
sessionId: parentPiboSessionId,
|
|
1636
|
+
gatewaySettings: loadPiboGatewaySettings(),
|
|
1637
|
+
});
|
|
1634
1638
|
if (resources)
|
|
1635
1639
|
resources.admission = admission.admission;
|
|
1636
1640
|
const reminderGeneration = this.runReminderGeneration(parentPiboSessionId);
|
|
@@ -5,6 +5,8 @@ import { sanitizeTelemetryRetentionSettings } from "./telemetry-retention-settin
|
|
|
5
5
|
import { sanitizeTelemetryStaleThresholdSettings } from "./telemetry-staleness.js";
|
|
6
6
|
export const DEFAULT_USER_TIMEZONE = "UTC";
|
|
7
7
|
export const DEFAULT_WEB_ANNOTATIONS_TOGGLE_SHORTCUT = "Alt+Shift+A";
|
|
8
|
+
export const DEFAULT_TRANSCRIPTION_PROVIDER_ID = "openai-chatgpt";
|
|
9
|
+
const LEGACY_OPENAI_TRANSCRIPTION_PROVIDER_ID = "openai";
|
|
8
10
|
export const DEFAULT_PIBO_USER_SETTINGS_PATH = "user-settings.json";
|
|
9
11
|
export function loadPiboUserSettings() {
|
|
10
12
|
return sanitizeUserSettings(readState().settings);
|
|
@@ -37,6 +39,23 @@ export function sanitizeShortcut(value) {
|
|
|
37
39
|
const shortcut = value.replace(/[\u0000-\u001f\u007f]/g, "").trim();
|
|
38
40
|
return shortcut && shortcut.length <= 80 ? shortcut : undefined;
|
|
39
41
|
}
|
|
42
|
+
export function sanitizeTranscriptionProviderId(value) {
|
|
43
|
+
if (typeof value !== "string")
|
|
44
|
+
return undefined;
|
|
45
|
+
const providerId = value.trim();
|
|
46
|
+
return /^[a-z0-9][a-z0-9._-]{0,79}$/i.test(providerId) ? providerId : undefined;
|
|
47
|
+
}
|
|
48
|
+
export function sanitizeTranscriptionSettings(value) {
|
|
49
|
+
const raw = value && typeof value === "object" && !Array.isArray(value)
|
|
50
|
+
? value
|
|
51
|
+
: {};
|
|
52
|
+
const providerId = sanitizeTranscriptionProviderId(raw.providerId);
|
|
53
|
+
return {
|
|
54
|
+
providerId: providerId === LEGACY_OPENAI_TRANSCRIPTION_PROVIDER_ID
|
|
55
|
+
? DEFAULT_TRANSCRIPTION_PROVIDER_ID
|
|
56
|
+
: providerId ?? DEFAULT_TRANSCRIPTION_PROVIDER_ID,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
40
59
|
export function sanitizeTimezone(value) {
|
|
41
60
|
if (typeof value !== "string")
|
|
42
61
|
return undefined;
|
|
@@ -58,6 +77,7 @@ function sanitizeUserSettings(value) {
|
|
|
58
77
|
return {
|
|
59
78
|
timezone: sanitizeTimezone(raw.timezone) ?? DEFAULT_USER_TIMEZONE,
|
|
60
79
|
shortcuts: sanitizeShortcutSettings(raw.shortcuts),
|
|
80
|
+
transcription: sanitizeTranscriptionSettings(raw.transcription),
|
|
61
81
|
telemetryStaleThresholds: sanitizeTelemetryStaleThresholdSettings(raw.telemetryStaleThresholds),
|
|
62
82
|
telemetryRetention: sanitizeTelemetryRetentionSettings(raw.telemetryRetention),
|
|
63
83
|
};
|
|
@@ -327,9 +327,9 @@ function attributesForOutputEvent(event) {
|
|
|
327
327
|
if (event.type === "thinking_started" || event.type === "thinking_delta" || event.type === "thinking_finished")
|
|
328
328
|
return { thinkingIndex: event.thinkingIndex, contentIndex: event.contentIndex };
|
|
329
329
|
if (event.type === "tool_call")
|
|
330
|
-
return { toolCallId: event.toolCallId, toolName: event.toolName, argsComplete: event.argsComplete };
|
|
330
|
+
return { toolCallId: event.toolCallId, toolName: event.toolName, argsComplete: event.argsComplete, intent: event.intent };
|
|
331
331
|
if (event.type === "tool_execution_started" || event.type === "tool_execution_updated" || event.type === "tool_execution_finished")
|
|
332
|
-
return { toolCallId: event.toolCallId, toolName: event.toolName, isError: "isError" in event ? event.isError : undefined };
|
|
332
|
+
return { toolCallId: event.toolCallId, toolName: event.toolName, isError: "isError" in event ? event.isError : undefined, intent: event.intent };
|
|
333
333
|
if (event.type === "subagent_session")
|
|
334
334
|
return { toolCallId: event.toolCallId, toolName: event.toolName, subagentName: event.subagentName, childPiboSessionId: event.childPiboSessionId, threadKey: event.threadKey };
|
|
335
335
|
if (event.type === "execution_result")
|
package/dist/debug/index.js
CHANGED
|
@@ -88,7 +88,8 @@ async function runDebugResources(args) {
|
|
|
88
88
|
}
|
|
89
89
|
const options = parseOptions(args);
|
|
90
90
|
const { collectGatewayResourceSnapshot, renderGatewayResourceSnapshotText } = await import("../core/gateway-resource-guard.js");
|
|
91
|
-
const
|
|
91
|
+
const { loadPiboGatewaySettings } = await import("../core/gateway-settings.js");
|
|
92
|
+
const snapshot = await collectGatewayResourceSnapshot({ gatewaySettings: loadPiboGatewaySettings() });
|
|
92
93
|
if (options.json)
|
|
93
94
|
console.log(JSON.stringify(snapshot, null, 2));
|
|
94
95
|
else
|
|
@@ -1088,9 +1089,13 @@ Environment:
|
|
|
1088
1089
|
PIBO_GATEWAY_MIN_HEAP_AVAILABLE_BYTES=<bytes>
|
|
1089
1090
|
PIBO_GATEWAY_MAX_RSS_BYTES=<bytes>
|
|
1090
1091
|
PIBO_GATEWAY_KNOWN_DAEMON_WARNING_RSS_BYTES=<bytes>
|
|
1091
|
-
PIBO_GATEWAY_MAX_CONCURRENT_YIELDED_RUNS=<count> (default:
|
|
1092
|
+
PIBO_GATEWAY_MAX_CONCURRENT_YIELDED_RUNS=<count> (default: 50)
|
|
1093
|
+
PIBO_SESSION_CONCURRENT_YIELDED_RUNS=<count> (default: 10)
|
|
1092
1094
|
PIBO_GATEWAY_YIELDED_RUN_MEMORY_RESERVATION_BYTES=<bytes> (default: 2147483648)
|
|
1093
1095
|
|
|
1096
|
+
Web settings:
|
|
1097
|
+
Settings -> Concurrency persists overrides that apply to new yielded runs immediately.
|
|
1098
|
+
|
|
1094
1099
|
Next:
|
|
1095
1100
|
pibo debug resources --json
|
|
1096
1101
|
pibo compute health --json
|
package/dist/gateway/server.js
CHANGED
|
@@ -429,6 +429,8 @@ export class PiboGatewayServer {
|
|
|
429
429
|
getProfiles: () => this.pluginRegistry.getProfileInfos(),
|
|
430
430
|
createProfile: (name) => this.pluginRegistry.createProfile(name),
|
|
431
431
|
getCapabilityCatalog: () => this.pluginRegistry.getCapabilityCatalog(),
|
|
432
|
+
getTranscriptionProviderInfos: () => this.pluginRegistry.getTranscriptionProviderInfos(),
|
|
433
|
+
transcribe: (providerId, input) => this.pluginRegistry.transcribe(providerId, input),
|
|
432
434
|
inspectAgentRuntimeInstances: () => this.pluginRegistry.inspectAgentRuntimeInstances(),
|
|
433
435
|
getAgentRuntimeAuthStatus: (runtimeInstanceId) => this.requireRouter().getAgentRuntimeAuthStatus(runtimeInstanceId),
|
|
434
436
|
startAgentRuntimeAuth: (runtimeInstanceId, input) => this.requireRouter().startAgentRuntimeAuth(runtimeInstanceId, input),
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
export { createDefaultPiboProfile, createDefaultPiboPluginRegistry, createDefaultPiboPlugins, createGatewayProducerPiboPluginRegistry, createGatewayProducerPiboProfile, CODEX_NATIVE_PROFILE_NAME, CODEX_NATIVE_RUNTIME_INSTANCE_ID, piboCodexNativePlugin, OMP_PROFILE_NAME, OMP_RUNTIME_INSTANCE_ID, piboOmpPlugin, piboCorePlugin, piboGatewayProducerPlugin, } from "./plugins/builtin.js";
|
|
2
2
|
export { createPiboBetterAuthPlugin } from "./plugins/better-auth.js";
|
|
3
3
|
export { createPiboChatWebPlugin } from "./plugins/chat-web.js";
|
|
4
|
+
export { createPiboOpenAiChatGptTranscriptionPlugin, piboOpenAiChatGptTranscriptionPlugin } from "./plugins/openai-chatgpt-transcription.js";
|
|
5
|
+
export { createPiboOpenAiTranscriptionPlugin, piboOpenAiTranscriptionPlugin } from "./plugins/openai-transcription.js";
|
|
4
6
|
export { createPiboContextFilesPlugin } from "./plugins/context-files.js";
|
|
5
7
|
export { createPiboWebHostPlugin } from "./plugins/web.js";
|
|
6
8
|
export { createChatWebApp } from "./apps/chat/web-app.js";
|
|
7
9
|
export { createBetterAuthService } from "./auth/better-auth.js";
|
|
10
|
+
export { DEFAULT_OPENAI_CHATGPT_TRANSCRIPTION_URL, DEFAULT_OPENAI_CHATGPT_USER_AGENT, OPENAI_CHATGPT_TRANSCRIPTION_PROVIDER_ID, OPENAI_CODEX_AUTH_PROVIDER_ID, createOpenAiChatGptTranscriptionProvider, } from "./transcription/openai-chatgpt.js";
|
|
11
|
+
export { DEFAULT_OPENAI_TRANSCRIPTION_MODEL, DEFAULT_OPENAI_TRANSCRIPTION_URL, OPENAI_API_CREDENTIAL_PROVIDER_ID, OPENAI_TRANSCRIPTION_PROVIDER_ID, createOpenAiTranscriptionProvider, } from "./transcription/openai.js";
|
|
12
|
+
export { PiboTranscriptionError } from "./transcription/types.js";
|
|
8
13
|
export { DEFAULT_AGENT_RUNTIME_INSTANCE_ID, InitialSessionContext, InitialSessionContextBuilder, normalizeToolProfile, } from "./core/profiles.js";
|
|
9
14
|
export { AgentRuntimeAdapterRegistry } from "./agent-runtime/registry.js";
|
|
10
15
|
export { validateAgentRuntimeProfileCapabilities } from "./agent-runtime/profile-validation.js";
|
package/dist/plugins/builtin.js
CHANGED
|
@@ -13,6 +13,8 @@ import { piboCodexNativePlugin } from "./codex-native.js";
|
|
|
13
13
|
import { addPiboNativeToolingContext, registerPiboNativeTooling } from "./native-tooling.js";
|
|
14
14
|
import { piboWebAnnotationsPlugin } from "./web-annotations.js";
|
|
15
15
|
import { piboOmpPlugin } from "./omp.js";
|
|
16
|
+
import { piboOpenAiChatGptTranscriptionPlugin } from "./openai-chatgpt-transcription.js";
|
|
17
|
+
import { piboOpenAiTranscriptionPlugin } from "./openai-transcription.js";
|
|
16
18
|
import { definePiboPlugin, PiboPluginRegistry } from "./registry.js";
|
|
17
19
|
import { PI_AGENT_RUNTIME_DRIVER } from "../agent-runtimes/pi/adapter.js";
|
|
18
20
|
export { createDefaultPiboProfile, DEFAULT_PIBO_PROFILE_NAME } from "../core/default-profile.js";
|
|
@@ -589,11 +591,11 @@ export const piboGatewayProducerPlugin = definePiboPlugin({
|
|
|
589
591
|
},
|
|
590
592
|
});
|
|
591
593
|
export function createDefaultPiboPlugins() {
|
|
592
|
-
return [piboCorePlugin, piboCodexNativePlugin, piboCodexCompatPlugin, piboWebAnnotationsPlugin, piboOmpPlugin];
|
|
594
|
+
return [piboCorePlugin, piboCodexNativePlugin, piboCodexCompatPlugin, piboWebAnnotationsPlugin, piboOmpPlugin, piboOpenAiChatGptTranscriptionPlugin, piboOpenAiTranscriptionPlugin];
|
|
593
595
|
}
|
|
594
596
|
export function createGatewayProducerPiboPluginRegistry() {
|
|
595
597
|
return PiboPluginRegistry.create({
|
|
596
|
-
plugins: [piboCorePlugin, piboCodexNativePlugin, piboGatewayProducerPlugin, piboCodexCompatPlugin, piboWebAnnotationsPlugin, piboOmpPlugin],
|
|
598
|
+
plugins: [piboCorePlugin, piboCodexNativePlugin, piboGatewayProducerPlugin, piboCodexCompatPlugin, piboWebAnnotationsPlugin, piboOmpPlugin, piboOpenAiChatGptTranscriptionPlugin, piboOpenAiTranscriptionPlugin],
|
|
597
599
|
});
|
|
598
600
|
}
|
|
599
601
|
export function createDefaultPiboPluginRegistry() {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createOpenAiChatGptTranscriptionProvider, } from "../transcription/openai-chatgpt.js";
|
|
2
|
+
import { definePiboPlugin } from "./registry.js";
|
|
3
|
+
export function createPiboOpenAiChatGptTranscriptionPlugin(options = {}) {
|
|
4
|
+
return definePiboPlugin({
|
|
5
|
+
id: "pibo.transcription.openai-chatgpt",
|
|
6
|
+
name: "ChatGPT Subscription Transcription",
|
|
7
|
+
register(api) {
|
|
8
|
+
api.registerTranscriptionProvider(createOpenAiChatGptTranscriptionProvider(options));
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
export const piboOpenAiChatGptTranscriptionPlugin = createPiboOpenAiChatGptTranscriptionPlugin();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createOpenAiTranscriptionProvider, } from "../transcription/openai.js";
|
|
2
|
+
import { definePiboPlugin } from "./registry.js";
|
|
3
|
+
export function createPiboOpenAiTranscriptionPlugin(options = {}) {
|
|
4
|
+
return definePiboPlugin({
|
|
5
|
+
id: "pibo.transcription.openai",
|
|
6
|
+
name: "OpenAI Transcription",
|
|
7
|
+
register(api) {
|
|
8
|
+
api.registerTranscriptionProvider(createOpenAiTranscriptionProvider(options));
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
export const piboOpenAiTranscriptionPlugin = createPiboOpenAiTranscriptionPlugin();
|
package/dist/plugins/registry.js
CHANGED
|
@@ -44,6 +44,7 @@ export class PiboPluginRegistry {
|
|
|
44
44
|
gatewaySlashCommands = new Map();
|
|
45
45
|
channels = new Map();
|
|
46
46
|
authService;
|
|
47
|
+
transcriptionProviders = new Map();
|
|
47
48
|
webApps = new Map();
|
|
48
49
|
capabilityPackages = new Map();
|
|
49
50
|
eventListeners = new Set();
|
|
@@ -181,6 +182,23 @@ export class PiboPluginRegistry {
|
|
|
181
182
|
}
|
|
182
183
|
this.authService = service;
|
|
183
184
|
}
|
|
185
|
+
registerTranscriptionProvider(provider) {
|
|
186
|
+
this.addUnique(this.transcriptionProviders, provider.id, provider, "transcription provider");
|
|
187
|
+
}
|
|
188
|
+
async getTranscriptionProviderInfos() {
|
|
189
|
+
return await Promise.all([...this.transcriptionProviders.values()].map(async (provider) => ({
|
|
190
|
+
id: provider.id,
|
|
191
|
+
name: provider.name,
|
|
192
|
+
description: provider.description,
|
|
193
|
+
configured: provider.isConfigured ? await Promise.resolve(provider.isConfigured()).catch(() => false) : true,
|
|
194
|
+
pluginId: provider.pluginId,
|
|
195
|
+
pluginName: provider.pluginId ? this.pluginNames.get(provider.pluginId) : undefined,
|
|
196
|
+
})));
|
|
197
|
+
}
|
|
198
|
+
async transcribe(providerId, input) {
|
|
199
|
+
const provider = this.getRequired(this.transcriptionProviders, providerId, "transcription provider");
|
|
200
|
+
return { providerId, ...await provider.transcribe(input) };
|
|
201
|
+
}
|
|
184
202
|
registerWebApp(app) {
|
|
185
203
|
if (this.webApps.has(app.name)) {
|
|
186
204
|
throw new Error(`Duplicate web app "${app.name}"`);
|
|
@@ -414,6 +432,10 @@ export class PiboPluginRegistry {
|
|
|
414
432
|
toolNames: [...pkg.toolNames],
|
|
415
433
|
pluginId: pkg.pluginId ?? pluginId,
|
|
416
434
|
});
|
|
435
|
+
const withPluginTranscriptionProviderContext = (provider) => ({
|
|
436
|
+
...provider,
|
|
437
|
+
pluginId: provider.pluginId ?? pluginId,
|
|
438
|
+
});
|
|
417
439
|
return {
|
|
418
440
|
registerAgentRuntimeDriver: (driver) => this.registerAgentRuntimeDriver(driver),
|
|
419
441
|
registerAgentRuntimeInstance: (instance) => this.registerAgentRuntimeInstance(instance),
|
|
@@ -430,6 +452,7 @@ export class PiboPluginRegistry {
|
|
|
430
452
|
registerGatewayAction: (action) => this.registerGatewayAction(action),
|
|
431
453
|
registerChannel: (channel) => this.registerChannel(channel),
|
|
432
454
|
registerAuthService: (service) => this.registerAuthService(service),
|
|
455
|
+
registerTranscriptionProvider: (provider) => this.registerTranscriptionProvider(withPluginTranscriptionProviderContext(provider)),
|
|
433
456
|
registerWebApp: (app) => this.registerWebApp(app),
|
|
434
457
|
registerCapabilityPackage: (pkg) => this.registerCapabilityPackage(withPluginPackageContext(pkg)),
|
|
435
458
|
registerLoopStopCondition: (condition) => this.registerLoopStopCondition(condition, pluginId),
|
|
@@ -18,7 +18,55 @@ export function buildCompactTerminalRows(traceView, options) {
|
|
|
18
18
|
.filter((item) => item.node.type !== "agent.turn" && (options.showThinking || item.node.type !== "model.reasoning"));
|
|
19
19
|
const candidates = syncThinkingToolRows(flatNodes.map((item) => createRowCandidate(item.node, item.turnId)));
|
|
20
20
|
applyCompletedTurnTiming(candidates, turnById);
|
|
21
|
-
|
|
21
|
+
const reconciled = reconcileConceptualRowCandidates(candidates);
|
|
22
|
+
const rows = (options.toolDisplayMode ?? "default") === "default"
|
|
23
|
+
? groupRelatedToolCandidates(reconciled).map((candidate) => candidate.row)
|
|
24
|
+
: reconciled.map((candidate) => candidate.row);
|
|
25
|
+
return applyToolDisplayMode(rows, options.toolDisplayMode ?? "default");
|
|
26
|
+
}
|
|
27
|
+
function applyToolDisplayMode(rows, mode) {
|
|
28
|
+
if (mode === "default")
|
|
29
|
+
return rows;
|
|
30
|
+
if (mode === "hide")
|
|
31
|
+
return rows.filter((row) => !isToolDisplayRow(row));
|
|
32
|
+
return rows.flatMap((row) => {
|
|
33
|
+
if (!isToolDisplayRow(row))
|
|
34
|
+
return [row];
|
|
35
|
+
const intent = row.intent?.trim();
|
|
36
|
+
if (mode === "intent" && !intent)
|
|
37
|
+
return [];
|
|
38
|
+
const slimRow = {
|
|
39
|
+
...row,
|
|
40
|
+
lines: row.lines.slice(0, 1),
|
|
41
|
+
input: undefined,
|
|
42
|
+
output: undefined,
|
|
43
|
+
error: undefined,
|
|
44
|
+
markdown: undefined,
|
|
45
|
+
expandable: false,
|
|
46
|
+
singleLine: true,
|
|
47
|
+
previewOmission: undefined,
|
|
48
|
+
detailItems: undefined,
|
|
49
|
+
};
|
|
50
|
+
if (mode !== "intent")
|
|
51
|
+
return [slimRow];
|
|
52
|
+
return [{
|
|
53
|
+
...slimRow,
|
|
54
|
+
title: undefined,
|
|
55
|
+
summary: undefined,
|
|
56
|
+
lines: [{
|
|
57
|
+
prefix: "bullet",
|
|
58
|
+
tokens: [token(intent, row.status === "error" ? "red" : row.status === "done" ? "green" : "cyan", "semibold")],
|
|
59
|
+
}],
|
|
60
|
+
}];
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
function isToolDisplayRow(row) {
|
|
64
|
+
return row.id.startsWith("terminal:tool:")
|
|
65
|
+
|| row.kind === "tool.call"
|
|
66
|
+
|| row.kind === "tool.image"
|
|
67
|
+
|| row.kind === "tool.group.exploring"
|
|
68
|
+
|| row.kind === "tool.group.images"
|
|
69
|
+
|| row.kind === "agent.delegation";
|
|
22
70
|
}
|
|
23
71
|
export function findActiveTurnStartedAt(traceView) {
|
|
24
72
|
if (!traceView)
|
|
@@ -112,6 +160,7 @@ function createRowCandidate(node, turnId) {
|
|
|
112
160
|
row: {
|
|
113
161
|
...candidate.row,
|
|
114
162
|
id: compactTerminalRowIdentity(node),
|
|
163
|
+
intent: node.intent,
|
|
115
164
|
...debugFields(node),
|
|
116
165
|
},
|
|
117
166
|
};
|
|
@@ -164,6 +213,7 @@ function reconcileConceptualRowCandidates(candidates) {
|
|
|
164
213
|
...existing.row,
|
|
165
214
|
...candidate.row,
|
|
166
215
|
sourceNodeIds: [...new Set([...existing.row.sourceNodeIds, ...candidate.row.sourceNodeIds])],
|
|
216
|
+
intent: candidate.row.intent ?? existing.row.intent,
|
|
167
217
|
input: candidate.row.input ?? existing.row.input,
|
|
168
218
|
output: candidate.row.output ?? existing.row.output,
|
|
169
219
|
error: candidate.row.error ?? existing.row.error,
|
|
@@ -465,6 +465,7 @@ function traceNodeFromEvent(piboSessionId, event, childByParent, linkedChildByTo
|
|
|
465
465
|
id: `tool:${event.toolCallId}`,
|
|
466
466
|
parentId: turnParentId,
|
|
467
467
|
toolCallId: event.toolCallId,
|
|
468
|
+
intent: event.intent,
|
|
468
469
|
type: subagentTool ? "agent.delegation" : "tool.call",
|
|
469
470
|
title: event.toolName,
|
|
470
471
|
status: event.type === "tool_execution_finished"
|
|
@@ -685,6 +686,8 @@ function isNativeHistoryToolEchoEvent(event, coverage, sessionStatus) {
|
|
|
685
686
|
event.type !== "tool_execution_updated" &&
|
|
686
687
|
event.type !== "tool_execution_finished")
|
|
687
688
|
return false;
|
|
689
|
+
if (typeof event.intent === "string" && event.intent.trim())
|
|
690
|
+
return false;
|
|
688
691
|
return coverage.toolCallIds.has(event.toolCallId) || historyCoversEvent(event, coverage);
|
|
689
692
|
}
|
|
690
693
|
export function mergeMessageTurnTimings(...groups) {
|
|
@@ -917,6 +920,7 @@ function thinkingEventNodeId(event) {
|
|
|
917
920
|
}
|
|
918
921
|
function mergeToolEvent(target, update) {
|
|
919
922
|
target.status = update.status;
|
|
923
|
+
target.intent = update.intent ?? target.intent;
|
|
920
924
|
target.summary = update.summary ?? target.summary;
|
|
921
925
|
target.input = mergeDelegationInput(target, update);
|
|
922
926
|
target.output = update.output ?? target.output;
|
|
@@ -80,6 +80,7 @@ function storedEventFromStreamEvent(event, piboSessionId, nextSequence, now) {
|
|
|
80
80
|
toolCallId: event.toolCallId,
|
|
81
81
|
toolName: event.toolName,
|
|
82
82
|
args: event.args,
|
|
83
|
+
...(event.intent ? { intent: event.intent } : {}),
|
|
83
84
|
};
|
|
84
85
|
return makeStored(event, piboSessionId, "tool_execution_started", payload, nextSequence, now);
|
|
85
86
|
}
|
|
@@ -95,6 +96,7 @@ function storedEventFromStreamEvent(event, piboSessionId, nextSequence, now) {
|
|
|
95
96
|
toolName: event.toolName,
|
|
96
97
|
args: event.args,
|
|
97
98
|
partialResult: event.partialResult,
|
|
99
|
+
...(event.intent ? { intent: event.intent } : {}),
|
|
98
100
|
}
|
|
99
101
|
: {
|
|
100
102
|
type: "tool_call",
|
|
@@ -104,6 +106,7 @@ function storedEventFromStreamEvent(event, piboSessionId, nextSequence, now) {
|
|
|
104
106
|
toolName: event.toolName,
|
|
105
107
|
args: event.args,
|
|
106
108
|
argsComplete: Boolean(event.argsComplete),
|
|
109
|
+
...(event.intent ? { intent: event.intent } : {}),
|
|
107
110
|
};
|
|
108
111
|
return makeStored(event, piboSessionId, sourceEventType, payload, nextSequence, now);
|
|
109
112
|
}
|
|
@@ -116,6 +119,7 @@ function storedEventFromStreamEvent(event, piboSessionId, nextSequence, now) {
|
|
|
116
119
|
toolName: event.toolName,
|
|
117
120
|
result: event.result,
|
|
118
121
|
isError: Boolean(event.isError),
|
|
122
|
+
...(event.intent ? { intent: event.intent } : {}),
|
|
119
123
|
};
|
|
120
124
|
return makeStored(event, piboSessionId, "tool_execution_finished", payload, nextSequence, now);
|
|
121
125
|
}
|
|
@@ -52,6 +52,7 @@ function traceNodeShallowEqual(left, right) {
|
|
|
52
52
|
left.eventId === right.eventId &&
|
|
53
53
|
left.toolCallId === right.toolCallId &&
|
|
54
54
|
left.runId === right.runId &&
|
|
55
|
+
left.intent === right.intent &&
|
|
55
56
|
left.type === right.type &&
|
|
56
57
|
left.title === right.title &&
|
|
57
58
|
left.status === right.status &&
|