@ai-sdk/harness-pi 1.0.64 → 1.0.65
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 +11 -0
- package/README.md +20 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +128 -30
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
- package/src/pi-harness.ts +16 -0
- package/src/pi-session.ts +150 -25
- package/src/pi-translate.ts +26 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# @ai-sdk/harness-pi
|
|
2
2
|
|
|
3
|
+
## 1.0.65
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- c20a315: feat(harness-pi): support caller-supplied inline Pi extension factories
|
|
8
|
+
- a03ff6c: feat(harness): add support for per-harness MCP servers
|
|
9
|
+
- Updated dependencies [401a4ba]
|
|
10
|
+
- Updated dependencies [a03ff6c]
|
|
11
|
+
- @ai-sdk/provider-utils@5.0.26
|
|
12
|
+
- @ai-sdk/harness@1.0.65
|
|
13
|
+
|
|
3
14
|
## 1.0.64
|
|
4
15
|
|
|
5
16
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -50,3 +50,23 @@ try {
|
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
The adapter requires a `HarnessV1SandboxProvider`. Pi has no in-sandbox bridge, so the sandbox doesn't need to expose any ports — `@ai-sdk/sandbox-vercel` or `@ai-sdk/sandbox-just-bash` both work.
|
|
53
|
+
|
|
54
|
+
## Inline extensions
|
|
55
|
+
|
|
56
|
+
Use `extensionFactories` to load trusted inline Pi extensions for each harness session:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { createPi } from '@ai-sdk/harness-pi';
|
|
60
|
+
|
|
61
|
+
const harness = createPi({
|
|
62
|
+
extensionFactories: [
|
|
63
|
+
pi => {
|
|
64
|
+
pi.on('agent_start', () => {
|
|
65
|
+
console.log('Pi agent started');
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Routine resource refreshes between turns do not reinitialize extension factories. If the underlying Pi session is rebuilt, factories initialize for the new Pi runtime. Extension factories execute in the host Node.js process, so only pass factories you trust. This option does not enable filesystem extension discovery: user, project, personal, and settings-based Pi extensions remain disabled. Themes and prompt templates also remain disabled.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as _ai_sdk_harness from '@ai-sdk/harness';
|
|
2
2
|
import { HarnessV1, HarnessV1BuiltinTool } from '@ai-sdk/harness';
|
|
3
|
+
import { ExtensionFactory } from '@earendil-works/pi-coding-agent';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Pi auth options. Exactly one of `gateway` or `customEnv` is honoured
|
|
@@ -52,6 +53,17 @@ type PiHarnessSettings = {
|
|
|
52
53
|
* model settings.
|
|
53
54
|
*/
|
|
54
55
|
readonly agentDir?: string;
|
|
56
|
+
/**
|
|
57
|
+
* MCP server definitions keyed by server name. Each definition uses the
|
|
58
|
+
* underlying runtime's native MCP server configuration format.
|
|
59
|
+
*/
|
|
60
|
+
readonly mcpServers?: Record<string, unknown>;
|
|
61
|
+
/**
|
|
62
|
+
* Trusted inline Pi extensions loaded for each harness session.
|
|
63
|
+
*
|
|
64
|
+
* Filesystem-discovered user and project extensions remain disabled.
|
|
65
|
+
*/
|
|
66
|
+
readonly extensionFactories?: ReadonlyArray<ExtensionFactory>;
|
|
55
67
|
};
|
|
56
68
|
declare const PI_BUILTIN_TOOLS: {
|
|
57
69
|
readonly read: HarnessV1BuiltinTool<{
|
package/dist/index.js
CHANGED
|
@@ -108,7 +108,7 @@ import { resolveSandboxHomeDir } from "@ai-sdk/harness/utils";
|
|
|
108
108
|
import { getAiGatewayAuthFromEnv } from "@ai-sdk/harness/utils";
|
|
109
109
|
|
|
110
110
|
// src/version.ts
|
|
111
|
-
var VERSION = true ? "1.0.
|
|
111
|
+
var VERSION = true ? "1.0.65" : "0.0.0-test";
|
|
112
112
|
|
|
113
113
|
// src/pi-auth.ts
|
|
114
114
|
var DEFAULT_GATEWAY_BASE_URL = "https://ai-gateway.vercel.sh";
|
|
@@ -740,6 +740,7 @@ async function writePiSkills(args) {
|
|
|
740
740
|
|
|
741
741
|
// src/pi-translate.ts
|
|
742
742
|
import { randomBytes } from "crypto";
|
|
743
|
+
import { secureJsonParse } from "@ai-sdk/provider-utils";
|
|
743
744
|
|
|
744
745
|
// src/pi-utils.ts
|
|
745
746
|
import {
|
|
@@ -804,7 +805,9 @@ function createPiTranslatorState(options = {}) {
|
|
|
804
805
|
pendingStepToolCallIds: /* @__PURE__ */ new Set(),
|
|
805
806
|
stepOpen: false,
|
|
806
807
|
hostToolResults: /* @__PURE__ */ new Map(),
|
|
808
|
+
dynamicToolCallIds: /* @__PURE__ */ new Set(),
|
|
807
809
|
builtinToolNames: new Set(options.builtinToolNames ?? []),
|
|
810
|
+
hostToolNames: new Set(options.hostToolNames ?? []),
|
|
808
811
|
nativeToCommonNameMap: map
|
|
809
812
|
};
|
|
810
813
|
}
|
|
@@ -830,6 +833,14 @@ function unwrapPiToolResult(event) {
|
|
|
830
833
|
if (typeof event.content === "string") return event.content;
|
|
831
834
|
return event.result ?? event.content ?? null;
|
|
832
835
|
}
|
|
836
|
+
function parseMcpToolResult(content) {
|
|
837
|
+
if (typeof content !== "string") return content;
|
|
838
|
+
try {
|
|
839
|
+
return secureJsonParse(content);
|
|
840
|
+
} catch {
|
|
841
|
+
return content;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
833
844
|
function resolveToolName(state, nativeName) {
|
|
834
845
|
const common = state.nativeToCommonNameMap.get(nativeName);
|
|
835
846
|
return { wire: common ?? nativeName, native: nativeName };
|
|
@@ -969,7 +980,9 @@ function translatePiEvent(event, state) {
|
|
|
969
980
|
if (!event.toolCallId || !event.toolName) return [];
|
|
970
981
|
const { wire, native } = resolveToolName(state, event.toolName);
|
|
971
982
|
state.observedToolNames.set(event.toolCallId, wire);
|
|
972
|
-
const
|
|
983
|
+
const isMcpTool = !state.hostToolNames.has(native) && (native === "mcp" || native.startsWith("mcp__"));
|
|
984
|
+
const providerExecuted = state.builtinToolNames.has(native) || isMcpTool;
|
|
985
|
+
if (isMcpTool) state.dynamicToolCallIds.add(event.toolCallId);
|
|
973
986
|
const input = serializeToolOutput(event.args ?? event.input ?? {});
|
|
974
987
|
return [
|
|
975
988
|
{
|
|
@@ -978,7 +991,8 @@ function translatePiEvent(event, state) {
|
|
|
978
991
|
toolName: wire,
|
|
979
992
|
input,
|
|
980
993
|
...wire !== native ? { nativeName: native } : {},
|
|
981
|
-
...providerExecuted ? { providerExecuted: true } : {}
|
|
994
|
+
...providerExecuted ? { providerExecuted: true } : {},
|
|
995
|
+
...isMcpTool ? { dynamic: true } : {}
|
|
982
996
|
}
|
|
983
997
|
];
|
|
984
998
|
}
|
|
@@ -989,7 +1003,8 @@ function translatePiEvent(event, state) {
|
|
|
989
1003
|
const nativeName = event.toolName;
|
|
990
1004
|
const wire = recordedName ?? (nativeName ? resolveToolName(state, nativeName).wire : void 0);
|
|
991
1005
|
if (!wire) return [];
|
|
992
|
-
const
|
|
1006
|
+
const dynamic = state.dynamicToolCallIds.delete(event.toolCallId);
|
|
1007
|
+
const result = state.hostToolResults.has(event.toolCallId) ? state.hostToolResults.get(event.toolCallId) ?? null : dynamic ? parseMcpToolResult(unwrapPiToolResult(event)) : unwrapPiToolResult(event);
|
|
993
1008
|
state.hostToolResults.delete(event.toolCallId);
|
|
994
1009
|
state.pendingStepToolCallIds.delete(event.toolCallId);
|
|
995
1010
|
return [
|
|
@@ -998,7 +1013,8 @@ function translatePiEvent(event, state) {
|
|
|
998
1013
|
toolCallId: event.toolCallId,
|
|
999
1014
|
toolName: wire,
|
|
1000
1015
|
result,
|
|
1001
|
-
...event.isError ? { isError: true } : {}
|
|
1016
|
+
...event.isError ? { isError: true } : {},
|
|
1017
|
+
...dynamic ? { dynamic: true } : {}
|
|
1002
1018
|
},
|
|
1003
1019
|
...finishStep(state)
|
|
1004
1020
|
];
|
|
@@ -1505,6 +1521,7 @@ async function syncHostWorkspaceFromSandbox(args) {
|
|
|
1505
1521
|
|
|
1506
1522
|
// src/pi-session.ts
|
|
1507
1523
|
var HARNESS_ID2 = "pi";
|
|
1524
|
+
var PI_MCP_ADAPTER_PACKAGE = "pi-mcp-adapter";
|
|
1508
1525
|
var parkedPiSessions = /* @__PURE__ */ new Map();
|
|
1509
1526
|
function isWithinDirectory(parent, child) {
|
|
1510
1527
|
const rel = path7.relative(parent, child);
|
|
@@ -1670,18 +1687,56 @@ async function createPiSession(input) {
|
|
|
1670
1687
|
env: resolverEnv
|
|
1671
1688
|
});
|
|
1672
1689
|
const resolvedModel = resolveModel(input.settings.model);
|
|
1690
|
+
const mcpServers = resolvePiMcpServers({
|
|
1691
|
+
mcpServers: input.settings.mcpServers
|
|
1692
|
+
});
|
|
1693
|
+
const hasMcpServers = Object.keys(mcpServers).length > 0;
|
|
1694
|
+
const extensionFactories = [
|
|
1695
|
+
...input.settings.extensionFactories ?? []
|
|
1696
|
+
];
|
|
1697
|
+
if (hasMcpServers) {
|
|
1698
|
+
const { createMcpAdapter } = await import(PI_MCP_ADAPTER_PACKAGE);
|
|
1699
|
+
extensionFactories.push(
|
|
1700
|
+
createMcpAdapter({
|
|
1701
|
+
config: {
|
|
1702
|
+
mcpServers,
|
|
1703
|
+
settings: {
|
|
1704
|
+
directTools: true,
|
|
1705
|
+
toolPrefix: "mcp",
|
|
1706
|
+
disableProxyTool: true
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
})
|
|
1710
|
+
);
|
|
1711
|
+
}
|
|
1712
|
+
const hasExtensionFactories = extensionFactories.length > 0;
|
|
1713
|
+
let preserveExtensionsResult = false;
|
|
1714
|
+
let currentExtensionsResult;
|
|
1673
1715
|
const resourceLoader = new DefaultResourceLoader({
|
|
1674
1716
|
cwd: sessionWorkDir,
|
|
1675
1717
|
agentDir: hostAgentDir,
|
|
1676
1718
|
settingsManager,
|
|
1677
1719
|
appendSystemPromptOverride: () => [],
|
|
1678
|
-
extensionFactories
|
|
1720
|
+
extensionFactories,
|
|
1721
|
+
...hasExtensionFactories ? {
|
|
1722
|
+
// DefaultResourceLoader invokes inline factories on every reload.
|
|
1723
|
+
// Resource-only reloads retain the active extension runtime, while a
|
|
1724
|
+
// genuine Pi session rebuild is allowed to replace that runtime.
|
|
1725
|
+
extensionsOverride: (extensions) => {
|
|
1726
|
+
if (preserveExtensionsResult && currentExtensionsResult != null) {
|
|
1727
|
+
return currentExtensionsResult;
|
|
1728
|
+
}
|
|
1729
|
+
currentExtensionsResult = extensions;
|
|
1730
|
+
return extensions;
|
|
1731
|
+
}
|
|
1732
|
+
} : {},
|
|
1679
1733
|
// Pi runs in the host process, so its default resource discovery reaches
|
|
1680
1734
|
// the host developer's personal config (`~/.pi/agent/*`, `~/.agents/*`).
|
|
1681
|
-
// The harness
|
|
1682
|
-
// disable
|
|
1683
|
-
// developer's personal Pi extensions inside
|
|
1684
|
-
//
|
|
1735
|
+
// The harness exposes only explicitly supplied inline extension factories;
|
|
1736
|
+
// disable filesystem extension discovery entirely to avoid loading and
|
|
1737
|
+
// executing a host developer's personal or project Pi extensions inside
|
|
1738
|
+
// the server process. Themes and prompt templates stay disabled. Skills
|
|
1739
|
+
// are kept but filtered to workspace project skills plus harness-provided
|
|
1685
1740
|
// skills whose files live in sandbox HOME.
|
|
1686
1741
|
noExtensions: true,
|
|
1687
1742
|
noThemes: true,
|
|
@@ -1697,6 +1752,20 @@ async function createPiSession(input) {
|
|
|
1697
1752
|
})
|
|
1698
1753
|
});
|
|
1699
1754
|
await resourceLoader.reload();
|
|
1755
|
+
async function reloadResourcesOnly() {
|
|
1756
|
+
if (!hasExtensionFactories) {
|
|
1757
|
+
await resourceLoader.reload();
|
|
1758
|
+
return;
|
|
1759
|
+
}
|
|
1760
|
+
const factories = extensionFactories.splice(0);
|
|
1761
|
+
preserveExtensionsResult = true;
|
|
1762
|
+
try {
|
|
1763
|
+
await resourceLoader.reload();
|
|
1764
|
+
} finally {
|
|
1765
|
+
preserveExtensionsResult = false;
|
|
1766
|
+
extensionFactories.push(...factories);
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1700
1769
|
let piSession;
|
|
1701
1770
|
let unsubscribe;
|
|
1702
1771
|
let lastToolsSignature;
|
|
@@ -1824,13 +1893,27 @@ async function createPiSession(input) {
|
|
|
1824
1893
|
builtinNames: [...builtinNames]
|
|
1825
1894
|
};
|
|
1826
1895
|
}
|
|
1896
|
+
async function disposePiSession() {
|
|
1897
|
+
unsubscribe?.();
|
|
1898
|
+
unsubscribe = void 0;
|
|
1899
|
+
const session = piSession;
|
|
1900
|
+
piSession = void 0;
|
|
1901
|
+
if (!session) return;
|
|
1902
|
+
if (hasMcpServers) {
|
|
1903
|
+
await session.reload().catch(() => {
|
|
1904
|
+
});
|
|
1905
|
+
}
|
|
1906
|
+
session.dispose();
|
|
1907
|
+
}
|
|
1827
1908
|
async function rebuildPiSession(userTools, isFirstBuild) {
|
|
1909
|
+
let resourcesReloaded = false;
|
|
1828
1910
|
if (piSession) {
|
|
1829
|
-
|
|
1830
|
-
unsubscribe = void 0;
|
|
1831
|
-
piSession.dispose();
|
|
1832
|
-
piSession = void 0;
|
|
1911
|
+
await disposePiSession();
|
|
1833
1912
|
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
1913
|
+
if (hasExtensionFactories) {
|
|
1914
|
+
await resourceLoader.reload();
|
|
1915
|
+
resourcesReloaded = true;
|
|
1916
|
+
}
|
|
1834
1917
|
}
|
|
1835
1918
|
const { customTools, builtinNames } = buildToolDefinitions(userTools);
|
|
1836
1919
|
const toolNames = customTools.map((t) => t.name);
|
|
@@ -1847,17 +1930,21 @@ async function createPiSession(input) {
|
|
|
1847
1930
|
settingsManager,
|
|
1848
1931
|
resourceLoader,
|
|
1849
1932
|
customTools,
|
|
1850
|
-
tools: toolNames,
|
|
1933
|
+
...hasMcpServers ? { noTools: "builtin" } : { tools: toolNames },
|
|
1851
1934
|
...input.settings.thinkingLevel ? { thinkingLevel: input.settings.thinkingLevel } : {},
|
|
1852
1935
|
...resolvedModel ? { model: resolvedModel } : {}
|
|
1853
1936
|
});
|
|
1854
1937
|
piSession = session;
|
|
1938
|
+
if (hasMcpServers) {
|
|
1939
|
+
await piSession.bindExtensions({ mode: "print" });
|
|
1940
|
+
}
|
|
1855
1941
|
const candidatePath = sessionManager.getSessionFile();
|
|
1856
1942
|
if (candidatePath) {
|
|
1857
1943
|
sessionFileName = safePiSessionFileName(path7.basename(candidatePath));
|
|
1858
1944
|
}
|
|
1859
1945
|
translatorState = createPiTranslatorState({
|
|
1860
1946
|
builtinToolNames: builtinNames,
|
|
1947
|
+
hostToolNames: userTools.map((tool2) => tool2.name),
|
|
1861
1948
|
nativeToCommon: NATIVE_TO_COMMON
|
|
1862
1949
|
});
|
|
1863
1950
|
unsubscribe = piSession.subscribe((rawEvent) => {
|
|
@@ -1872,6 +1959,7 @@ async function createPiSession(input) {
|
|
|
1872
1959
|
}
|
|
1873
1960
|
}
|
|
1874
1961
|
});
|
|
1962
|
+
return resourcesReloaded;
|
|
1875
1963
|
}
|
|
1876
1964
|
async function runTurn(turnOpts) {
|
|
1877
1965
|
if (stopped) {
|
|
@@ -1880,11 +1968,14 @@ async function createPiSession(input) {
|
|
|
1880
1968
|
const userTools = turnOpts.tools;
|
|
1881
1969
|
const signature = JSON.stringify(userTools.map((t) => t.name).sort());
|
|
1882
1970
|
const needsRebuild = piSession == null || signature !== lastToolsSignature;
|
|
1971
|
+
let resourcesReloaded = false;
|
|
1883
1972
|
if (needsRebuild) {
|
|
1884
|
-
await rebuildPiSession(userTools, piSession == null);
|
|
1973
|
+
resourcesReloaded = await rebuildPiSession(userTools, piSession == null);
|
|
1885
1974
|
lastToolsSignature = signature;
|
|
1886
1975
|
}
|
|
1887
|
-
|
|
1976
|
+
if (!resourcesReloaded) {
|
|
1977
|
+
await reloadResourcesOnly();
|
|
1978
|
+
}
|
|
1888
1979
|
await syncHostWorkspaceFromSandbox({
|
|
1889
1980
|
sandbox,
|
|
1890
1981
|
sandboxWorkDir: input.sessionWorkDir,
|
|
@@ -1893,6 +1984,7 @@ async function createPiSession(input) {
|
|
|
1893
1984
|
currentEmit = turnOpts.emit;
|
|
1894
1985
|
translatorState = createPiTranslatorState({
|
|
1895
1986
|
builtinToolNames: [...PI_NATIVE_BUILTIN_NAMES],
|
|
1987
|
+
hostToolNames: userTools.map((tool2) => tool2.name),
|
|
1896
1988
|
nativeToCommon: NATIVE_TO_COMMON
|
|
1897
1989
|
});
|
|
1898
1990
|
turnOpts.emit({ type: "stream-start" });
|
|
@@ -1974,10 +2066,7 @@ async function createPiSession(input) {
|
|
|
1974
2066
|
} catch {
|
|
1975
2067
|
}
|
|
1976
2068
|
}
|
|
1977
|
-
|
|
1978
|
-
unsubscribe = void 0;
|
|
1979
|
-
piSession?.dispose();
|
|
1980
|
-
piSession = void 0;
|
|
2069
|
+
await disposePiSession();
|
|
1981
2070
|
workspaceVfs.unmount();
|
|
1982
2071
|
await rm2(hostRoot, { recursive: true, force: true });
|
|
1983
2072
|
return {
|
|
@@ -2036,10 +2125,7 @@ async function createPiSession(input) {
|
|
|
2036
2125
|
parkedPiSessions.delete(input.sessionId);
|
|
2037
2126
|
settlePendingToolResults("Pi session stopped");
|
|
2038
2127
|
settlePendingToolApprovals("Pi session stopped");
|
|
2039
|
-
|
|
2040
|
-
unsubscribe = void 0;
|
|
2041
|
-
piSession?.dispose();
|
|
2042
|
-
piSession = void 0;
|
|
2128
|
+
await disposePiSession();
|
|
2043
2129
|
workspaceVfs.unmount();
|
|
2044
2130
|
await rm2(hostRoot, { recursive: true, force: true });
|
|
2045
2131
|
},
|
|
@@ -2094,10 +2180,7 @@ async function createPiSession(input) {
|
|
|
2094
2180
|
parkedPiSessions.delete(input.sessionId);
|
|
2095
2181
|
settlePendingToolResults("Pi session suspended");
|
|
2096
2182
|
settlePendingToolApprovals("Pi session suspended");
|
|
2097
|
-
|
|
2098
|
-
unsubscribe = void 0;
|
|
2099
|
-
piSession?.dispose();
|
|
2100
|
-
piSession = void 0;
|
|
2183
|
+
await disposePiSession();
|
|
2101
2184
|
workspaceVfs.unmount();
|
|
2102
2185
|
await rm2(hostRoot, { recursive: true, force: true });
|
|
2103
2186
|
return {
|
|
@@ -2110,6 +2193,19 @@ async function createPiSession(input) {
|
|
|
2110
2193
|
};
|
|
2111
2194
|
return sessionImpl;
|
|
2112
2195
|
}
|
|
2196
|
+
function resolvePiMcpServers({
|
|
2197
|
+
mcpServers
|
|
2198
|
+
}) {
|
|
2199
|
+
if (mcpServers == null) return {};
|
|
2200
|
+
for (const [name, value] of Object.entries(mcpServers)) {
|
|
2201
|
+
if (value == null || typeof value !== "object" || Array.isArray(value)) {
|
|
2202
|
+
throw new Error(
|
|
2203
|
+
`Pi MCP server ${JSON.stringify(name)} must be configured with an object value.`
|
|
2204
|
+
);
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
return mcpServers;
|
|
2208
|
+
}
|
|
2113
2209
|
function isAbortError(value) {
|
|
2114
2210
|
if (value == null) return false;
|
|
2115
2211
|
if (typeof value === "object" && value.name === "AbortError") {
|
|
@@ -2428,7 +2524,9 @@ function createPi(settings = {}) {
|
|
|
2428
2524
|
settings: {
|
|
2429
2525
|
...settings.auth ? { auth: settings.auth } : {},
|
|
2430
2526
|
...settings.model ? { model: settings.model } : {},
|
|
2431
|
-
...settings.thinkingLevel ? { thinkingLevel: settings.thinkingLevel } : {}
|
|
2527
|
+
...settings.thinkingLevel ? { thinkingLevel: settings.thinkingLevel } : {},
|
|
2528
|
+
...settings.mcpServers ? { mcpServers: settings.mcpServers } : {},
|
|
2529
|
+
...settings.extensionFactories ? { extensionFactories: settings.extensionFactories } : {}
|
|
2432
2530
|
},
|
|
2433
2531
|
clientApp: PI_CLIENT_APP,
|
|
2434
2532
|
isResume: lifecycleState != null,
|