@animalabs/agent-framework 0.14.0 → 0.15.0
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/src/agent.d.ts +10 -0
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +39 -0
- package/dist/src/agent.js.map +1 -1
- package/dist/src/framework.d.ts +5 -2
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +126 -36
- package/dist/src/framework.js.map +1 -1
- package/dist/src/modules/history/index.d.ts +69 -0
- package/dist/src/modules/history/index.d.ts.map +1 -0
- package/dist/src/modules/history/index.js +519 -0
- package/dist/src/modules/history/index.js.map +1 -0
- package/dist/src/modules/history/search-regex-worker.d.ts +23 -0
- package/dist/src/modules/history/search-regex-worker.d.ts.map +1 -0
- package/dist/src/modules/history/search-regex-worker.js +57 -0
- package/dist/src/modules/history/search-regex-worker.js.map +1 -0
- package/dist/src/modules/index.d.ts +1 -0
- package/dist/src/modules/index.d.ts.map +1 -1
- package/dist/src/modules/index.js +1 -0
- package/dist/src/modules/index.js.map +1 -1
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +42 -1
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/types.d.ts +5 -0
- package/dist/src/modules/workspace/types.d.ts.map +1 -1
- package/dist/src/modules/workspace/types.js.map +1 -1
- package/dist/src/types/trace.d.ts +4 -0
- package/dist/src/types/trace.d.ts.map +1 -1
- package/dist/test/history-module.test.d.ts +2 -0
- package/dist/test/history-module.test.d.ts.map +1 -0
- package/dist/test/history-module.test.js +449 -0
- package/dist/test/history-module.test.js.map +1 -0
- package/dist/test/kv-unified-open-flight-retry.test.d.ts +2 -0
- package/dist/test/kv-unified-open-flight-retry.test.d.ts.map +1 -0
- package/dist/test/kv-unified-open-flight-retry.test.js +189 -0
- package/dist/test/kv-unified-open-flight-retry.test.js.map +1 -0
- package/dist/test/kv-unified-wiring.test.js +39 -0
- package/dist/test/kv-unified-wiring.test.js.map +1 -1
- package/dist/test/puppet-tool-call.test.js +113 -1
- package/dist/test/puppet-tool-call.test.js.map +1 -1
- package/dist/test/puppet-turn-reservation.test.d.ts +2 -0
- package/dist/test/puppet-turn-reservation.test.d.ts.map +1 -0
- package/dist/test/puppet-turn-reservation.test.js +139 -0
- package/dist/test/puppet-turn-reservation.test.js.map +1 -0
- package/dist/test/save-recent-image.test.js +29 -0
- package/dist/test/save-recent-image.test.js.map +1 -1
- package/dist/test/tool-result-spill.test.js +114 -0
- package/dist/test/tool-result-spill.test.js.map +1 -1
- package/dist/test/workspace-read-text.test.d.ts +2 -0
- package/dist/test/workspace-read-text.test.d.ts.map +1 -0
- package/dist/test/workspace-read-text.test.js +101 -0
- package/dist/test/workspace-read-text.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/agent.ts +41 -0
- package/src/framework.ts +132 -43
- package/src/modules/history/index.ts +613 -0
- package/src/modules/history/search-regex-worker.ts +76 -0
- package/src/modules/index.ts +2 -0
- package/src/modules/workspace/index.ts +42 -1
- package/src/modules/workspace/types.ts +5 -0
- package/src/types/trace.ts +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@animalabs/agent-framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Multi-agent framework with pluggable modules, persistent state, and concurrent tool execution",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -46,8 +46,8 @@
|
|
|
46
46
|
"author": "Anima Research",
|
|
47
47
|
"license": "MIT",
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@animalabs/chronicle": "^0.
|
|
50
|
-
"@animalabs/context-manager": "^0.
|
|
49
|
+
"@animalabs/chronicle": "^0.4.0",
|
|
50
|
+
"@animalabs/context-manager": "^0.9.2",
|
|
51
51
|
"@animalabs/membrane": "^0.5.78",
|
|
52
52
|
"chokidar": "^4.0.3",
|
|
53
53
|
"discord.js": "^14.25.1",
|
package/src/agent.ts
CHANGED
|
@@ -99,6 +99,11 @@ export class Agent {
|
|
|
99
99
|
private _state: AgentState = { status: 'idle' };
|
|
100
100
|
private _inferenceStartedAt = 0;
|
|
101
101
|
private _streamId = 0;
|
|
102
|
+
/** kv-unified receipt flights opened by the CURRENT activation and not yet
|
|
103
|
+
* settled by a usage event. Held on the agent (not only in the stream's
|
|
104
|
+
* closure) so the next activation can close whatever its predecessor left
|
|
105
|
+
* open — see failOpenKvSubmissions. */
|
|
106
|
+
private kvOpenQueue: Array<{ submissionId: string; wireReceipt: CacheWireReceipt }> | null = null;
|
|
102
107
|
lastStreamInputTokens = 0;
|
|
103
108
|
/** Real prefix size of the last usage event: fresh + cache creation +
|
|
104
109
|
* cache read. THE window-shaped number — `lastStreamInputTokens` alone
|
|
@@ -730,6 +735,19 @@ export class Agent {
|
|
|
730
735
|
throw new Error(`Agent ${this.name} cannot start stream in state ${this._state.status}`);
|
|
731
736
|
}
|
|
732
737
|
|
|
738
|
+
// A new activation supersedes every receipt flight the previous one left
|
|
739
|
+
// open. Membrane fires the kv-unified wire receipt per provider attempt,
|
|
740
|
+
// BEFORE the adapter call; only that attempt's usage event settles it.
|
|
741
|
+
// When the attempt dies without usage (transport error, idle timeout,
|
|
742
|
+
// framework cancel for a budget restart or endTurn) the flight stays open
|
|
743
|
+
// until the old driveStream's `finally` runs — and every successor path
|
|
744
|
+
// (error-policy retry, budget restart, the next wake after an abort)
|
|
745
|
+
// starts the new stream BEFORE that `finally`. The successor's first
|
|
746
|
+
// receipt then met the ledger's single-flight guard and failed the
|
|
747
|
+
// recovery itself: "kv-unified submission devops:46:…:4 is still in
|
|
748
|
+
// flight" (devops agent, 2026-09-16 07:33Z, no provider error logged).
|
|
749
|
+
this.failOpenKvSubmissions();
|
|
750
|
+
|
|
733
751
|
this._streamId++;
|
|
734
752
|
this._inferenceStartedAt = Date.now();
|
|
735
753
|
this.lastStreamInputTokens = 0;
|
|
@@ -753,6 +771,7 @@ export class Agent {
|
|
|
753
771
|
const kvQueue: Array<{ submissionId: string; wireReceipt: CacheWireReceipt }> = [];
|
|
754
772
|
let kvCall = 0;
|
|
755
773
|
if (kvEnabled) {
|
|
774
|
+
this.kvOpenQueue = kvQueue;
|
|
756
775
|
const layoutHash = stableHash(request.messages);
|
|
757
776
|
const kvRequest = request as NormalizedRequest & KvUnifiedRequestHooks;
|
|
758
777
|
kvRequest.onCacheWireReceipt = (receipt) => {
|
|
@@ -791,6 +810,28 @@ export class Agent {
|
|
|
791
810
|
};
|
|
792
811
|
}
|
|
793
812
|
|
|
813
|
+
/** Close every kv-unified receipt flight the previous activation left
|
|
814
|
+
* unsettled. Idempotent: the framework's driveStream `finally` drains the
|
|
815
|
+
* same per-stream queue, so whichever runs first empties it and the other
|
|
816
|
+
* finds nothing. Failing an already-settled id is a no-op in the ledger. */
|
|
817
|
+
private failOpenKvSubmissions(): void {
|
|
818
|
+
const open = this.kvOpenQueue?.splice(0) ?? [];
|
|
819
|
+
this.kvOpenQueue = null;
|
|
820
|
+
if (open.length === 0) return;
|
|
821
|
+
const strategy = (this.contextManager as unknown as { getStrategy?: () => unknown })
|
|
822
|
+
.getStrategy?.() as { reportKvUnifiedFailed?: (submissionId: string) => void } | undefined;
|
|
823
|
+
for (const { submissionId } of open) {
|
|
824
|
+
console.error(
|
|
825
|
+
`[kv-unified] ${this.name}: closing receipt flight ${submissionId} left open by the previous activation`,
|
|
826
|
+
);
|
|
827
|
+
try {
|
|
828
|
+
strategy?.reportKvUnifiedFailed?.(submissionId);
|
|
829
|
+
} catch (err) {
|
|
830
|
+
console.error(`[kv-unified] ${this.name}: could not close flight ${submissionId}:`, err);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
794
835
|
/**
|
|
795
836
|
* Transition to waiting_for_tools when stream yields tool calls.
|
|
796
837
|
* Called by framework's driveStream.
|
package/src/framework.ts
CHANGED
|
@@ -6132,6 +6132,23 @@ export class AgentFramework {
|
|
|
6132
6132
|
this.releasePrimaryProviderGate(agent.name);
|
|
6133
6133
|
return;
|
|
6134
6134
|
}
|
|
6135
|
+
// The scheduler's turn-alive test passed BEFORE the park; the wait
|
|
6136
|
+
// is the one gap in which another turn-alive holder can take the
|
|
6137
|
+
// agent (puppetToolCall's reservation, #145). Never replace its
|
|
6138
|
+
// token — the re-entry would set ours over it, and a short turn
|
|
6139
|
+
// here ends and flushes before the puppeted tool returns, leaving
|
|
6140
|
+
// its pair queued behind a flush that already happened. Give
|
|
6141
|
+
// admission back and hand the wake to the scheduler, which keeps
|
|
6142
|
+
// it queued while the agent is turn-alive and starts it normally
|
|
6143
|
+
// once the holder releases.
|
|
6144
|
+
if (this.activeTurnTokens.has(agent.name)) {
|
|
6145
|
+
this.releasePrimaryProviderGate(agent.name);
|
|
6146
|
+
this.pendingRequests.push(trigger ?? {
|
|
6147
|
+
agentName: agent.name, reason: 'provider-admission:requeue', source: 'scheduler', timestamp: Date.now(),
|
|
6148
|
+
});
|
|
6149
|
+
console.error(`[provider-admission] ${agent.name}: turn-alive while parked — wake requeued, not started`);
|
|
6150
|
+
return;
|
|
6151
|
+
}
|
|
6135
6152
|
await this.startAgentStream(agent, trigger, attempt, true);
|
|
6136
6153
|
}).catch((error) => {
|
|
6137
6154
|
this.releasePrimaryProviderGate(agent.name);
|
|
@@ -7786,8 +7803,11 @@ export class AgentFramework {
|
|
|
7786
7803
|
* affordance to a model that cannot find it (older models especially).
|
|
7787
7804
|
*
|
|
7788
7805
|
* Semantics:
|
|
7789
|
-
* - Refused unless the agent is idle
|
|
7790
|
-
* the turn state machine and the live stream's
|
|
7806
|
+
* - Refused unless the agent is idle with no turn alive: puppeting
|
|
7807
|
+
* mid-turn would corrupt the turn state machine and the live stream's
|
|
7808
|
+
* wire ordering. For its own duration the puppet HOLDS the turn-alive
|
|
7809
|
+
* marker (wakes requeue, cross-turn writers defer), so a wake landing
|
|
7810
|
+
* during the tool's execution cannot start a turn underneath the pair.
|
|
7791
7811
|
* - Refused for tools outside the agent's own surface (canUseTool): the
|
|
7792
7812
|
* stored pair must be an act the agent could genuinely have taken.
|
|
7793
7813
|
* - The call executes FOR REAL through the shared dispatch (MCPL,
|
|
@@ -7811,9 +7831,13 @@ export class AgentFramework {
|
|
|
7811
7831
|
): Promise<{ toolUseId: string; result: ToolResult }> {
|
|
7812
7832
|
const agent = this.agents.get(agentName);
|
|
7813
7833
|
if (!agent) throw new Error(`Unknown agent: ${agentName}`);
|
|
7814
|
-
|
|
7834
|
+
// Idle AND no turn alive: status reads 'idle' from dequeue until the
|
|
7835
|
+
// stream registers, and again while a turn's teardown is pending
|
|
7836
|
+
// (the scheduler's own busy test, 'idle+turn-alive').
|
|
7837
|
+
if (agent.state.status !== 'idle' || this.activeTurnTokens.has(agentName)) {
|
|
7838
|
+
const shown = agent.state.status === 'idle' ? 'idle+turn-alive' : agent.state.status;
|
|
7815
7839
|
throw new Error(
|
|
7816
|
-
`puppet refused: agent ${agentName} is ${
|
|
7840
|
+
`puppet refused: agent ${agentName} is ${shown} (requires idle — ` +
|
|
7817
7841
|
`injecting a turn under an active stream corrupts wire ordering)`,
|
|
7818
7842
|
);
|
|
7819
7843
|
}
|
|
@@ -7833,44 +7857,94 @@ export class AgentFramework {
|
|
|
7833
7857
|
for (let i = 0; i < 22; i++) suffix += alphabet[Math.floor(Math.random() * alphabet.length)];
|
|
7834
7858
|
const toolUseId = `toolu_01${suffix}`;
|
|
7835
7859
|
|
|
7836
|
-
|
|
7837
|
-
|
|
7838
|
-
|
|
7839
|
-
|
|
7840
|
-
|
|
7841
|
-
|
|
7842
|
-
|
|
7843
|
-
|
|
7844
|
-
|
|
7845
|
-
//
|
|
7846
|
-
//
|
|
7847
|
-
|
|
7848
|
-
|
|
7849
|
-
|
|
7850
|
-
|
|
7851
|
-
|
|
7852
|
-
|
|
7853
|
-
|
|
7854
|
-
|
|
7855
|
-
|
|
7856
|
-
|
|
7857
|
-
|
|
7858
|
-
|
|
7859
|
-
|
|
7860
|
+
// Reserve the agent against turn start for the WHOLE operation, before
|
|
7861
|
+
// the first await (#145). The idle check above is a point in time; the
|
|
7862
|
+
// tool execution and the result build both await, and a wake arriving
|
|
7863
|
+
// in either gap used to start a real turn — then the pair below wrote
|
|
7864
|
+
// straight through the context manager into a turn that never saw it,
|
|
7865
|
+
// the exact wire-order corruption this method exists to avoid. Holding
|
|
7866
|
+
// a turn token is the same invariant the scheduler respects (turn-alive
|
|
7867
|
+
// requeues wakes) and the addMessage guard reads (cross-turn writers
|
|
7868
|
+
// defer), so for the duration this behaves like a turn with no stream.
|
|
7869
|
+
// Token-matched release in finally, same leak-proofing as
|
|
7870
|
+
// startAgentStream: a token nobody clears wedges the agent.
|
|
7871
|
+
const turnToken = this.nextTurnToken++;
|
|
7872
|
+
this.activeTurnTokens.set(agentName, turnToken);
|
|
7873
|
+
let ownedToEnd = false;
|
|
7874
|
+
try {
|
|
7875
|
+
const started = Date.now();
|
|
7876
|
+
const result = await this.executeToolCall({
|
|
7877
|
+
id: toolUseId,
|
|
7878
|
+
name: toolName,
|
|
7879
|
+
input,
|
|
7880
|
+
callerAgentName: agentName,
|
|
7881
|
+
});
|
|
7882
|
+
const durationMs = Date.now() - started;
|
|
7883
|
+
|
|
7884
|
+
// Store the pair through the same shapes the ordinary path uses. Build
|
|
7885
|
+
// the result blocks BEFORE storing the tool_use: the spill path awaits.
|
|
7886
|
+
// A message arriving during either await is deferred by the turn
|
|
7887
|
+
// token (as during a real tool call) and lands AFTER the pair, never
|
|
7888
|
+
// between tool_use and its tool_result. The two addMessage calls
|
|
7889
|
+
// below are synchronous and adjacent — nothing can interleave.
|
|
7890
|
+
const { blocks } = await this.buildStoredToolResultContent(
|
|
7891
|
+
agentName,
|
|
7892
|
+
[{ id: toolUseId, name: toolName, input, result, durationMs }],
|
|
7893
|
+
this.resolveToolResultInlineCap(agent).cap,
|
|
7894
|
+
);
|
|
7895
|
+
const toolUse: ContentBlock[] = [
|
|
7896
|
+
{ type: 'tool_use', id: toolUseId, name: toolName, input } as ContentBlock,
|
|
7897
|
+
];
|
|
7898
|
+
// Defensive: no path replaces a live token any more (the provider-
|
|
7899
|
+
// admission re-entry re-tests turn-alive and requeues instead), but
|
|
7900
|
+
// if one ever does, the side effect has happened and the pair must
|
|
7901
|
+
// still be stored — not under that turn: queue it as a unit for the
|
|
7902
|
+
// next flush (drainDeferredFor keeps order; the tool_result entry is
|
|
7903
|
+
// never split from its tool_use because both ride the same drain).
|
|
7904
|
+
// The finally below flushes it itself if that turn is already over.
|
|
7905
|
+
ownedToEnd = this.activeTurnTokens.get(agentName) === turnToken;
|
|
7906
|
+
if (ownedToEnd) {
|
|
7907
|
+
const cm = agent.getContextManager();
|
|
7908
|
+
cm.addMessage(agentName, toolUse);
|
|
7909
|
+
cm.addMessage('user', blocks);
|
|
7910
|
+
} else {
|
|
7911
|
+
this.deferredMessages.push(
|
|
7912
|
+
{ participant: agentName, content: toolUse, forAgent: agentName },
|
|
7913
|
+
{ participant: 'user', content: blocks, forAgent: agentName },
|
|
7914
|
+
);
|
|
7915
|
+
}
|
|
7860
7916
|
|
|
7861
|
-
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
|
|
7865
|
-
|
|
7866
|
-
|
|
7867
|
-
|
|
7868
|
-
|
|
7869
|
-
|
|
7870
|
-
|
|
7871
|
-
|
|
7872
|
-
|
|
7873
|
-
|
|
7917
|
+
this.emitTrace({
|
|
7918
|
+
type: 'puppet:tool-call',
|
|
7919
|
+
agentName,
|
|
7920
|
+
toolName,
|
|
7921
|
+
toolUseId,
|
|
7922
|
+
isError: !!result.isError,
|
|
7923
|
+
durationMs,
|
|
7924
|
+
...(ownedToEnd ? {} : { deferred: true }),
|
|
7925
|
+
});
|
|
7926
|
+
console.log(
|
|
7927
|
+
`[puppet] ${agentName}: ${toolName} → ${result.isError ? 'ERROR' : 'ok'} ` +
|
|
7928
|
+
`in ${durationMs}ms (${toolUseId})${ownedToEnd ? '' : ' — deferred behind a live turn'}`,
|
|
7929
|
+
);
|
|
7930
|
+
return { toolUseId, result };
|
|
7931
|
+
} finally {
|
|
7932
|
+
if (this.activeTurnTokens.get(agentName) === turnToken) this.activeTurnTokens.delete(agentName);
|
|
7933
|
+
// Messages deferred while we held the token land now, after the pair
|
|
7934
|
+
// — the same end-of-turn flush driveStream performs, under the same
|
|
7935
|
+
// tool-cycle guard. (No wake is requested: the agent sees the pair,
|
|
7936
|
+
// and anything that arrived meanwhile, on its next turn.) Keyed on
|
|
7937
|
+
// "no turn alive", not on token ownership: if a turn did replace us
|
|
7938
|
+
// and has already ended, its flush ran before our pair was queued —
|
|
7939
|
+
// nobody else will flush it, so we do.
|
|
7940
|
+
if (!this.activeTurnTokens.has(agentName)
|
|
7941
|
+
&& this.deferredMessages.length > 0 && this.pendingAssistantBlocks.size === 0) {
|
|
7942
|
+
for (const msg of this.drainDeferredFor(agentName)) {
|
|
7943
|
+
this.addMessage(msg.participant, msg.content, msg.metadata,
|
|
7944
|
+
msg.forAgent ? { forAgent: msg.forAgent } : undefined);
|
|
7945
|
+
}
|
|
7946
|
+
}
|
|
7947
|
+
}
|
|
7874
7948
|
}
|
|
7875
7949
|
|
|
7876
7950
|
private async executeToolCallFrom(call: ToolCall, origin: ChannelToolOrigin): Promise<ToolResult> {
|
|
@@ -8417,10 +8491,16 @@ export class AgentFramework {
|
|
|
8417
8491
|
try {
|
|
8418
8492
|
const result = await workspace.writeBinary(path, Buffer.from(content, 'utf8'), 'text/plain');
|
|
8419
8493
|
if (result.success) {
|
|
8494
|
+
// JSON serialization can expand each read-back code unit to six
|
|
8495
|
+
// chars (\\u0000). Leave room for the page's metadata and a possible
|
|
8496
|
+
// extra code unit at a surrogate-pair boundary.
|
|
8497
|
+
const pageOverhead = JSON.stringify(path).length + 256;
|
|
8498
|
+
const readLimit = Math.max(1, Math.min(2000, Math.floor((cap - pageOverhead) / 6) - 1));
|
|
8420
8499
|
return {
|
|
8421
8500
|
text: head
|
|
8422
|
-
+ `\n\n[truncated — showing ${head.length} of ${content.length} chars; full content: workspace file ${path}
|
|
8423
|
-
+
|
|
8501
|
+
+ `\n\n[truncated — showing ${head.length} of ${content.length} chars; full content: workspace file ${path}`
|
|
8502
|
+
+ `. Page with workspace--read ${JSON.stringify({ path, offsetChars: 0, limitChars: readLimit })}; `
|
|
8503
|
+
+ 'use nextOffsetChars as offsetChars until null, keeping limitChars. Or raise the inline cap via '
|
|
8424
8504
|
+ 'agent_settings update tool_result_inline_max_chars.]'
|
|
8425
8505
|
+ kept,
|
|
8426
8506
|
filePath: path,
|
|
@@ -8773,11 +8853,18 @@ export class AgentFramework {
|
|
|
8773
8853
|
// Blob threshold: 10KB - typical context-heavy requests exceed this
|
|
8774
8854
|
const BLOB_THRESHOLD = 10000;
|
|
8775
8855
|
|
|
8856
|
+
// Both branches persist the JSON view, never the live object: a compiled
|
|
8857
|
+
// request carries non-JSON members (the kv-unified `onCacheWireReceipt`
|
|
8858
|
+
// hook is a function) and Chronicle's JSON bridge rejects those with
|
|
8859
|
+
// "JS functions cannot be represented as a serde_json::Value" — which,
|
|
8860
|
+
// thrown from the failure path, masked the failure being logged.
|
|
8776
8861
|
if (entry.request && typeof entry.request === 'object') {
|
|
8777
8862
|
const requestJson = JSON.stringify(entry.request);
|
|
8778
8863
|
if (requestJson.length > BLOB_THRESHOLD) {
|
|
8779
8864
|
const blobId = this.store.storeBlob(Buffer.from(requestJson), 'application/json');
|
|
8780
8865
|
entryToStore.request = { blobId };
|
|
8866
|
+
} else {
|
|
8867
|
+
entryToStore.request = JSON.parse(requestJson);
|
|
8781
8868
|
}
|
|
8782
8869
|
}
|
|
8783
8870
|
|
|
@@ -8786,6 +8873,8 @@ export class AgentFramework {
|
|
|
8786
8873
|
if (responseJson.length > BLOB_THRESHOLD) {
|
|
8787
8874
|
const blobId = this.store.storeBlob(Buffer.from(responseJson), 'application/json');
|
|
8788
8875
|
entryToStore.response = { blobId };
|
|
8876
|
+
} else {
|
|
8877
|
+
entryToStore.response = JSON.parse(responseJson);
|
|
8789
8878
|
}
|
|
8790
8879
|
}
|
|
8791
8880
|
|