@newheap/platform-ai-chat 0.11.0 → 0.13.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.
@@ -37,11 +37,16 @@ class NhAssistantMockAdmin {
37
37
  this.contextVersions.push({ version: 1, hash: hashText(admin.context ?? ''), updatedAt: now(), updatedBy: null, text: admin.context ?? '' });
38
38
  const agents = admin.agents
39
39
  ?? scenario.agents.map(agent => ({ ...codeAgentFromSummary(agent), source: 'code' }));
40
- for (const { source, skills, shadowOf, ...input } of agents) {
40
+ for (const { source, skills, shadowOf, shadow, serviceAccount, ...input } of agents) {
41
+ const agentShadow = shadowOf !== undefined || shadow === true;
41
42
  this.agents.set(input.id, {
42
43
  agent: {
43
44
  ...this.toAgent(input, source, 1, false),
44
45
  shadowOf: shadowOf ?? null,
46
+ shadow: shadowState(agentShadow, shadowOf !== undefined || shadow === true, null, shadowOf !== undefined),
47
+ serviceAccount: serviceAccount === undefined
48
+ ? null
49
+ : { actorId: serviceAccount, configuredActorId: serviceAccount, overridden: false, changedAt: null, changedBy: null, reason: null },
45
50
  skills: (skills ?? []).map((skill) => ({
46
51
  skillId: skill.skillId,
47
52
  description: skill.description,
@@ -55,7 +60,8 @@ class NhAssistantMockAdmin {
55
60
  dailyToolCalls: null,
56
61
  dailyTokens: null,
57
62
  cancelledRuns: 0,
58
- startedRuns: 0
63
+ startedRuns: 0,
64
+ shadow: shadowState(agentShadow || skill.shadow === true, skill.shadow === true, null, shadowOf !== undefined)
59
65
  }))
60
66
  },
61
67
  codeDefinition: source === 'code' ? structuredClone(input) : null
@@ -148,6 +154,9 @@ class NhAssistantMockAdmin {
148
154
  if (id !== undefined && action === 'skills' && child !== undefined && segments[5] === 'state' && segments.length === 6) {
149
155
  return this.skillStateEndpoint(method, id, child, body);
150
156
  }
157
+ if (id !== undefined && action === 'skills' && child !== undefined && segments[5] === 'shadow' && segments.length === 6) {
158
+ return this.skillShadowEndpoint(method, id, child, body);
159
+ }
151
160
  return this.agentsEndpoint(method, id, action, body, segments.length);
152
161
  case 'mcp-servers':
153
162
  return this.serversEndpoint(method, id, action, child, body, segments.length);
@@ -228,6 +237,12 @@ class NhAssistantMockAdmin {
228
237
  if (length === 4 && action === 'state') {
229
238
  return this.agentStateEndpoint(stored, method, body);
230
239
  }
240
+ if (length === 4 && action === 'shadow' && method === 'POST') {
241
+ return this.agentShadowEndpoint(stored, body);
242
+ }
243
+ if (length === 4 && action === 'service-account' && method === 'POST') {
244
+ return this.serviceAccountEndpoint(stored, body);
245
+ }
231
246
  if (length === 4 && action === 'kill-switch' && method === 'POST') {
232
247
  const engaged = body?.engaged;
233
248
  if (typeof engaged !== 'boolean') {
@@ -286,6 +301,106 @@ class NhAssistantMockAdmin {
286
301
  stored.agent = { ...stored.agent, skills: stored.agent.skills.map(item => item.skillId === skillId ? changed : item) };
287
302
  return { status: 200, body: changed };
288
303
  }
304
+ /**
305
+ * `POST admin/agents/{id}/shadow`: the agent's shadow mode as an override of the code. A
306
+ * variant always works in shadow; the skills' effective modes follow the agent's.
307
+ */
308
+ agentShadowEndpoint(stored, body) {
309
+ const request = shadowRequest(body);
310
+ if (request === null) {
311
+ return validationFailure();
312
+ }
313
+ const current = stored.agent.shadow ?? shadowState(false, false, null, false);
314
+ if (current.locked) {
315
+ return failure(409, 'assistant-shadow-variant');
316
+ }
317
+ const override = request.mode === 'code' ? null : request.mode === 'shadow';
318
+ if (current.override !== override) {
319
+ const shadow = override ?? current.configured;
320
+ stored.agent = {
321
+ ...stored.agent,
322
+ shadow: { ...current, shadow, override, changedAt: this.now(), changedBy: 'current-user', reason: request.reason },
323
+ skills: (stored.agent.skills ?? []).map(skill => ({ ...skill, shadow: withAgentShadow(skill.shadow, shadow) }))
324
+ };
325
+ }
326
+ return { status: 200, body: toAgentState(stored.agent) };
327
+ }
328
+ /**
329
+ * `POST admin/agents/{id}/skills/{skillId}/shadow`: one skill's shadow mode; a skill of an agent
330
+ * in shadow works in shadow whatever its own mode.
331
+ */
332
+ skillShadowEndpoint(method, agentId, skillId, body) {
333
+ const stored = this.agents.get(agentId);
334
+ const skill = stored?.agent.skills?.find(item => item.skillId === skillId);
335
+ if (!stored || !skill) {
336
+ return failure(404, 'assistant-skill-not-found');
337
+ }
338
+ if (method !== 'POST') {
339
+ return { status: 405 };
340
+ }
341
+ const request = shadowRequest(body);
342
+ if (request === null) {
343
+ return validationFailure();
344
+ }
345
+ const agentShadow = stored.agent.shadow ?? shadowState(false, false, null, false);
346
+ if (agentShadow.locked) {
347
+ return failure(409, 'assistant-shadow-variant');
348
+ }
349
+ const current = skill.shadow ?? shadowState(agentShadow.shadow, false, null, false);
350
+ const override = request.mode === 'code' ? null : request.mode === 'shadow';
351
+ const changed = current.override === override
352
+ ? skill
353
+ : {
354
+ ...skill,
355
+ shadow: {
356
+ ...current,
357
+ shadow: agentShadow.shadow || (override ?? current.configured),
358
+ override,
359
+ changedAt: this.now(),
360
+ changedBy: 'current-user',
361
+ reason: request.reason
362
+ }
363
+ };
364
+ stored.agent = { ...stored.agent, skills: stored.agent.skills.map(item => item.skillId === skillId ? changed : item) };
365
+ return { status: 200, body: changed };
366
+ }
367
+ /**
368
+ * `POST admin/agents/{id}/service-account`: the technical account of an agent that answers to
369
+ * one in code. The mock accepts every account id except those starting with `unknown`.
370
+ */
371
+ serviceAccountEndpoint(stored, body) {
372
+ const current = stored.agent.serviceAccount;
373
+ if (!current) {
374
+ return failure(409, 'assistant-service-account-not-used');
375
+ }
376
+ const request = body;
377
+ if (request === undefined || (request.actorId !== null && request.actorId !== undefined && typeof request.actorId !== 'string')) {
378
+ return validationFailure();
379
+ }
380
+ const actorId = typeof request.actorId === 'string' && request.actorId.trim().length > 0 ? request.actorId.trim() : null;
381
+ if (actorId !== null && (actorId.length > 256 || actorId.startsWith('unknown'))) {
382
+ return actorId.length > 256 ? validationFailure() : failure(400, 'assistant-service-account-not-found');
383
+ }
384
+ const reason = typeof request.reason === 'string' && request.reason.trim().length > 0 ? request.reason.trim() : null;
385
+ if (reason !== null && reason.length > maxStateReason) {
386
+ return validationFailure();
387
+ }
388
+ const overridden = actorId !== null;
389
+ if ((current.overridden ? current.actorId : null) !== actorId) {
390
+ stored.agent = {
391
+ ...stored.agent,
392
+ serviceAccount: {
393
+ ...current,
394
+ actorId: actorId ?? current.configuredActorId,
395
+ overridden,
396
+ changedAt: this.now(),
397
+ changedBy: 'current-user',
398
+ reason
399
+ }
400
+ };
401
+ }
402
+ return { status: 200, body: toAgentState(stored.agent) };
403
+ }
289
404
  /**
290
405
  * `GET` and `POST admin/agents/{id}/state`. The mock runs no agents on their own, so a pause
291
406
  * collects no waiting events and a stop cancels no runs; the version never changes.
@@ -631,7 +746,9 @@ class NhAssistantMockAdmin {
631
746
  stateReason: previous?.stateReason ?? null,
632
747
  waitingInputs: previous?.waitingInputs ?? 0,
633
748
  skills: previous?.skills ?? [],
634
- shadowOf: previous?.shadowOf ?? null
749
+ shadowOf: previous?.shadowOf ?? null,
750
+ shadow: previous?.shadow ?? shadowState(false, false, null, false),
751
+ serviceAccount: previous?.serviceAccount ?? null
635
752
  };
636
753
  }
637
754
  toServer(stored) {
@@ -668,9 +785,27 @@ function toAgentState(agent) {
668
785
  cancelledRuns: 0,
669
786
  startedRuns: 0,
670
787
  skills: agent.skills ?? [],
671
- shadowOf: agent.shadowOf ?? null
788
+ shadowOf: agent.shadowOf ?? null,
789
+ shadow: agent.shadow ?? shadowState(false, false, null, false),
790
+ serviceAccount: agent.serviceAccount ?? null
672
791
  };
673
792
  }
793
+ function shadowState(shadow, configured, override, locked) {
794
+ return { shadow, configured, override, locked, changedAt: null, changedBy: null, reason: null };
795
+ }
796
+ /** A skill's mode after its agent's mode changed: the agent's shadow comes on top of its own. */
797
+ function withAgentShadow(skill, agentShadow) {
798
+ const own = skill?.override ?? skill?.configured ?? false;
799
+ return { ...(skill ?? shadowState(false, false, null, false)), shadow: agentShadow || own };
800
+ }
801
+ function shadowRequest(body) {
802
+ const request = body;
803
+ if (request?.mode !== 'shadow' && request?.mode !== 'live' && request?.mode !== 'code') {
804
+ return null;
805
+ }
806
+ const reason = typeof request.reason === 'string' && request.reason.trim().length > 0 ? request.reason.trim() : null;
807
+ return reason !== null && reason.length > maxStateReason ? null : { mode: request.mode, reason };
808
+ }
674
809
  function toServerFields(input) {
675
810
  return {
676
811
  id: input.id,