@nauticana/sail 1.1.33 → 1.1.35

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.
Files changed (61) hide show
  1. package/README.md +36 -23
  2. package/package.json +1 -1
  3. package/src/agent/component/approval_inbox.html +110 -0
  4. package/src/agent/component/approval_inbox.ts +129 -0
  5. package/src/agent/component/assistant_activity.html +28 -0
  6. package/src/agent/component/assistant_activity.ts +40 -0
  7. package/src/agent/component/decision_timeline.html +4 -4
  8. package/src/agent/component/decision_timeline.ts +2 -0
  9. package/src/agent/component/prompt_section_editor.html +4 -3
  10. package/src/agent/component/prompt_section_editor.ts +12 -5
  11. package/src/agent/component/release_sections.html +1 -1
  12. package/src/agent/component/release_sections.ts +2 -1
  13. package/src/agent/component/studio_page.html +303 -0
  14. package/src/agent/component/studio_page.ts +403 -0
  15. package/src/agent/component/studio_tab.ts +14 -0
  16. package/src/agent/component/turn_event_list.html +9 -0
  17. package/src/agent/component/turn_thread.html +27 -0
  18. package/src/agent/component/turn_thread.ts +83 -0
  19. package/src/agent/index.ts +8 -0
  20. package/src/agent/model/agent_studio.ts +185 -9
  21. package/src/agent/model/approval.ts +78 -0
  22. package/src/agent/model/conversation.ts +40 -1
  23. package/src/agent/model/decision.ts +14 -9
  24. package/src/agent/model/turn_event.ts +10 -0
  25. package/src/agent/service/agent_studio.service.ts +65 -1
  26. package/src/agent/service/approval.service.ts +38 -0
  27. package/src/agent/service/conversation.service.ts +10 -0
  28. package/src/agent/util/release_diff.ts +33 -0
  29. package/src/agent/util/studio_errors.ts +11 -0
  30. package/src/agent/util/turn_timeline.ts +7 -1
  31. package/src/component/agency/agency_earnings_summary.html +3 -3
  32. package/src/component/agency/agency_earnings_summary.ts +2 -2
  33. package/src/component/agency/agency_payout_history.html +1 -1
  34. package/src/component/agency/agency_payout_history.ts +2 -3
  35. package/src/component/agency/commission_ledger.html +1 -1
  36. package/src/component/agency/commission_ledger.ts +2 -3
  37. package/src/component/billing/plan_selector.ts +1 -1
  38. package/src/component/billing/price_selector.ts +5 -5
  39. package/src/component/login/account_deletion.html +17 -4
  40. package/src/component/login/account_deletion.ts +35 -28
  41. package/src/component/login/social_login.ts +6 -5
  42. package/src/component/payout/bank_info_form.html +15 -6
  43. package/src/component/payout/bank_info_form.ts +28 -72
  44. package/src/component/security/api_keys.html +78 -0
  45. package/src/component/security/api_keys.ts +90 -0
  46. package/src/component/security/connected_apps.html +25 -0
  47. package/src/component/security/connected_apps.ts +40 -0
  48. package/src/index.ts +8 -0
  49. package/src/model/access.ts +28 -0
  50. package/src/model/appdata.ts +41 -67
  51. package/src/model/auth.ts +17 -2
  52. package/src/model/geo.ts +12 -0
  53. package/src/service/access.service.ts +57 -0
  54. package/src/service/auth.service.ts +10 -4
  55. package/src/service/caption_pipe.ts +32 -0
  56. package/src/service/currency.service.ts +51 -0
  57. package/src/service/label.service.ts +5 -4
  58. package/src/service/money_pipe.ts +27 -0
  59. package/src/service/rest_service.ts +1 -1
  60. package/src/service/rest_url.ts +4 -0
  61. package/src/util/money.ts +18 -34
@@ -1,6 +1,7 @@
1
- /** Agent Studio `studio-v2` wire types. Field names are the backend's JSON names. */
1
+ /** Agent Studio wire types (Scout v1.2.7). Field names are the backend's JSON names. */
2
2
 
3
- export type AgentMergeMode = 'append' | 'replace';
3
+ /** R replace, A append, I intersect (non-prompt settings only). */
4
+ export type AgentMergeMode = 'R' | 'A' | 'I';
4
5
  export type AgentResetScope = 'agent_override' | 'type_default' | 'platform_baseline';
5
6
 
6
7
  export interface AgentSummary {
@@ -10,6 +11,7 @@ export interface AgentSummary {
10
11
  purpose: string;
11
12
  enabled: boolean;
12
13
  is_default: boolean;
14
+ archived: boolean;
13
15
  readiness: string;
14
16
  readiness_reason: string;
15
17
  type_defaults_revision: number;
@@ -35,10 +37,6 @@ export interface AgentModelSelection {
35
37
  video_model: string;
36
38
  }
37
39
 
38
- export interface AgentApprovalPolicy {
39
- require_approval: boolean;
40
- }
41
-
42
40
  /** One contribution to a prompt section. `editable` is response-only. */
43
41
  export interface AgentPromptLayer {
44
42
  scope_id: string;
@@ -48,6 +46,8 @@ export interface AgentPromptLayer {
48
46
  editable: boolean;
49
47
  instruction: string;
50
48
  output: string;
49
+ /** Who last edited the layer; absent on the baseline. */
50
+ bound_by?: number;
51
51
  }
52
52
 
53
53
  export interface AgentPromptSection {
@@ -77,7 +77,6 @@ export interface AgentDraft {
77
77
  display_name: string;
78
78
  enabled: boolean;
79
79
  is_default: boolean;
80
- approval_policy: AgentApprovalPolicy;
81
80
  models: AgentModelSelection;
82
81
  languages: AgentLanguageDraft[];
83
82
  agent_scope_id?: string;
@@ -103,6 +102,7 @@ export interface AgentTestResult {
103
102
  latency_ms: number;
104
103
  input_tokens: number;
105
104
  output_tokens: number;
105
+ /** Cost in minor units. */
106
106
  credits: number;
107
107
  sections: string[];
108
108
  }
@@ -110,6 +110,10 @@ export interface AgentTestResult {
110
110
  export interface AgentPublishRequest {
111
111
  agent_name: string;
112
112
  change_summary: string;
113
+ /** 1–99 deploys the release as a canary. */
114
+ canary_percentage?: number;
115
+ /** RFC 3339; a future time schedules the release. */
116
+ effective_at?: string;
113
117
  expected_agent_revision: number;
114
118
  expected_type_defaults_revision: number;
115
119
  }
@@ -137,6 +141,7 @@ export interface AgentSetDefaultRequest {
137
141
  export interface AgentSetEnabledRequest {
138
142
  agent_name: string;
139
143
  enabled: boolean;
144
+ reason?: string;
140
145
  expected_agent_revision: number;
141
146
  }
142
147
 
@@ -151,19 +156,41 @@ export interface AgentRelease {
151
156
  version: number;
152
157
  enabled: boolean;
153
158
  models: AgentModelSelection;
154
- require_approval: boolean;
155
159
  definition_digest: string;
156
160
  change_summary: string;
157
161
  published_by: number;
158
162
  published_at: string;
159
163
  active: boolean;
160
164
  languages: string[];
165
+ activation?: ReleaseActivation;
166
+ }
167
+
168
+ /** A scheduled publish; state P pending, A activated, R refused, X cancelled. */
169
+ export interface ReleaseActivation {
170
+ id: number;
171
+ agent_name: string;
172
+ version: number;
173
+ definition_digest: string;
174
+ canary_percentage?: number;
175
+ effective_at: string;
176
+ state: 'P' | 'A' | 'R' | 'X';
177
+ scheduled_by: string;
178
+ scheduled_at: string;
179
+ attempts: number;
180
+ next_attempt_at?: string;
181
+ reason?: string;
182
+ detail?: string;
183
+ decided_by?: string;
184
+ decided_at?: string;
161
185
  }
162
186
 
163
187
  export interface AgentAuditEvent {
164
188
  event: string;
165
189
  detail: string;
166
- user_id: number;
190
+ actor_kind?: string;
191
+ actor_id?: string;
192
+ /** Set only when a person acted. */
193
+ user_id?: number;
167
194
  event_time: string;
168
195
  }
169
196
 
@@ -196,3 +223,152 @@ export interface StudioModel {
196
223
  image_credits: number;
197
224
  video_credits_per_sec: number;
198
225
  }
226
+
227
+ /** One optional Studio API the backend mounted. Unknown ids are ignored; grants still decide. */
228
+ export interface StudioFeature {
229
+ id: string;
230
+ version: number;
231
+ actions: string[];
232
+ /** Authorization object of the actions; empty is AGENT_STUDIO. */
233
+ object?: string;
234
+ }
235
+
236
+ /** `copy_from` and `template_id` are exclusive. */
237
+ export interface AgentCreateRequest {
238
+ agent_type: string;
239
+ display_name: string;
240
+ copy_from?: string;
241
+ template_id?: string;
242
+ template_version?: number;
243
+ references?: Record<string, Record<string, string>>;
244
+ idempotency_key: string;
245
+ }
246
+
247
+ export interface AgentRenameRequest {
248
+ agent_name: string;
249
+ display_name: string;
250
+ expected_agent_revision: number;
251
+ }
252
+
253
+ export interface AgentArchiveRequest {
254
+ agent_name: string;
255
+ expected_agent_revision: number;
256
+ }
257
+
258
+ export interface AgentSettingSource {
259
+ scope_id: string;
260
+ scope_kind: string;
261
+ resource_version: string;
262
+ merge_mode: AgentMergeMode;
263
+ sealed: boolean;
264
+ }
265
+
266
+ /** One binding on the agent's scope chain; `editable` marks the agent's and its type's scopes. */
267
+ export interface AgentSetting {
268
+ scope_id: string;
269
+ scope_kind: string;
270
+ editable: boolean;
271
+ resource_kind: string;
272
+ resource_id: string;
273
+ resource_version: string;
274
+ merge_mode: AgentMergeMode;
275
+ sealed: boolean;
276
+ value: unknown;
277
+ }
278
+
279
+ export interface AgentEffectiveSetting {
280
+ resource_kind: string;
281
+ resource_id: string;
282
+ value: unknown;
283
+ source: AgentSettingSource;
284
+ superseded?: AgentSettingSource[];
285
+ }
286
+
287
+ /** Bindings other than prompt sections; `problem` says why they do not compile. */
288
+ export interface AgentSettings {
289
+ agent_name: string;
290
+ agent_scope_id: string;
291
+ type_scope_id: string;
292
+ bindings: AgentSetting[];
293
+ effective: AgentEffectiveSetting[];
294
+ problem?: string;
295
+ expected_agent_revision: number;
296
+ expected_type_defaults_revision: number;
297
+ }
298
+
299
+ /** Binds, or with `remove` ends, one resource at the agent or type scope. */
300
+ export interface AgentSettingChange {
301
+ agent_name: string;
302
+ scope_id: string;
303
+ resource_kind: string;
304
+ resource_id: string;
305
+ merge_mode?: AgentMergeMode;
306
+ sealed: boolean;
307
+ value?: unknown;
308
+ remove: boolean;
309
+ expected_agent_revision: number;
310
+ expected_type_defaults_revision: number;
311
+ }
312
+
313
+ /** Captions for Scout's fixed `AgentReadiness` values, which no constant domain carries. */
314
+ export const AGENT_READINESS_CAPTIONS: Record<string, string> = {
315
+ ready: 'Ready',
316
+ disabled: 'Disabled',
317
+ missing_model: 'No text model',
318
+ unpublished: 'Unpublished',
319
+ error: 'Invalid definition',
320
+ not_entitled: 'Not entitled',
321
+ needs_integration: 'Needs integration',
322
+ forbidden: 'Forbidden',
323
+ };
324
+
325
+ /** One enforced guardrail rule; `params` is the kind's JSON parameters. */
326
+ export interface GuardrailRule {
327
+ id: string;
328
+ kind: string;
329
+ stages?: string[];
330
+ action: string;
331
+ severity: string;
332
+ params?: unknown;
333
+ }
334
+
335
+ /** Rules an agent inherits from one layer: B baseline, P platform, T tenant, Y type. */
336
+ export interface InheritedGuardrails {
337
+ layer: string;
338
+ digest?: string;
339
+ rules: GuardrailRule[];
340
+ }
341
+
342
+ /** The layers an agent inherits, widest first, and its own rules, which only add or tighten. */
343
+ export interface AgentGuardrails {
344
+ agent_name: string;
345
+ inherited: InheritedGuardrails[];
346
+ rules: GuardrailRule[];
347
+ /** Why the agent's rules no longer fit the inherited ones. */
348
+ problem?: string;
349
+ expected_agent_revision: number;
350
+ }
351
+
352
+ /** Replaces the agent's own rules as a whole; an empty list removes them. */
353
+ export interface AgentGuardrailChange {
354
+ agent_name: string;
355
+ rules: GuardrailRule[];
356
+ expected_agent_revision: number;
357
+ }
358
+
359
+ /** One stored guardrail hit: rule ids and attribution, never the inspected content. */
360
+ export interface SafetyEvent {
361
+ id: number;
362
+ version?: string;
363
+ policy_version?: string;
364
+ stage: string;
365
+ layer: string;
366
+ action: string;
367
+ severity: string;
368
+ rule_ids: string[];
369
+ actor_kind?: string;
370
+ actor_id?: string;
371
+ request_id?: string;
372
+ duration_ms: number;
373
+ occurred_at: string;
374
+ }
@@ -0,0 +1,78 @@
1
+ import { PrincipalRef } from './decision';
2
+
3
+ /** Scout approval desk and MCP confirmation inbox wire types (v1.2.13). Codes render through backend captions. */
4
+
5
+ /** A approved, X rejected, E edited (tool-call proposals only, with revised arguments). */
6
+ export type ApprovalVerdict = 'A' | 'X' | 'E';
7
+
8
+ export interface ApproverReference {
9
+ kind: string;
10
+ ref: string;
11
+ }
12
+
13
+ /** One decision a reviewer owes; evidence stays behind its own authorization. */
14
+ export interface ApprovalRequest {
15
+ request_id: string;
16
+ execution_step_id: number;
17
+ conversation_id: string;
18
+ scope_id?: string;
19
+ rule_id: string;
20
+ action: string;
21
+ resource: string;
22
+ output_class: string;
23
+ risk_tier: string;
24
+ summary: string;
25
+ proposed_digest: string;
26
+ status: string;
27
+ deadline_at?: string;
28
+ created_at: string;
29
+ revises_step_id?: number;
30
+ rule_source: string;
31
+ route_id?: string;
32
+ route_step: number;
33
+ approver_reference?: ApproverReference;
34
+ }
35
+
36
+ export interface ApprovalResolveRequest {
37
+ request_id: string;
38
+ execution_step_id: number;
39
+ verdict: ApprovalVerdict;
40
+ proposed_digest: string;
41
+ reason?: string;
42
+ /** The JSON object an `E` verdict runs the tool call with instead. */
43
+ revised_arguments?: Record<string, unknown>;
44
+ }
45
+
46
+ /** `mcp_confirmation_status`: P pending, A approved, R running, D executed, F failed, U unknown, X declined, W withdrawn, T expired. */
47
+ export type ConfirmationStatus = 'P' | 'A' | 'R' | 'D' | 'F' | 'U' | 'X' | 'W' | 'T';
48
+
49
+ export interface ConfirmationKey {
50
+ tenant_id: number;
51
+ id: number;
52
+ }
53
+
54
+ /** One MCP tool call awaiting or past a person's confirmation; never its payload. */
55
+ export interface Confirmation extends ConfirmationKey {
56
+ maker: PrincipalRef;
57
+ client_ref?: string;
58
+ tool: string;
59
+ payload_digest: string;
60
+ /** The product's preview of the complete action. */
61
+ preview?: unknown;
62
+ channel: string;
63
+ status: ConfirmationStatus;
64
+ approval_required: boolean;
65
+ expired: boolean;
66
+ expires_at: string;
67
+ created_at: string;
68
+ decided_at?: string;
69
+ decider?: PrincipalRef;
70
+ decision_note?: string;
71
+ error?: string;
72
+ completed_at?: string;
73
+ reconciler?: PrincipalRef;
74
+ reconcile_note?: string;
75
+ can_decide: boolean;
76
+ can_withdraw: boolean;
77
+ can_reconcile: boolean;
78
+ }
@@ -4,7 +4,15 @@ import { InjectionToken } from '@angular/core';
4
4
  export interface ConversationTurnRequest {
5
5
  request_id: string;
6
6
  conversation_id: string;
7
- agent_id: string;
7
+ /** The agent type alias; its default agent serves a new conversation. */
8
+ agent_type: string;
9
+ /** One agent of that type instead of its default. */
10
+ agent_name?: string;
11
+ /** Pins a new conversation to one release; needs `release_reason`. */
12
+ release?: string;
13
+ release_reason?: string;
14
+ /** The product's opaque reference to the work the turn serves. */
15
+ work_ref?: string;
8
16
  /** Opaque product payload; the backend forwards it to the agent unread. */
9
17
  input: unknown;
10
18
  }
@@ -28,6 +36,37 @@ export class ConversationStreamFailure extends Error {
28
36
  }
29
37
  }
30
38
 
39
+ /** A `turn_status` code; C, F and X are terminal, as Scout's catalog marks them. */
40
+ export type TurnStatus = 'Q' | 'R' | 'S' | 'U' | 'H' | 'O' | 'C' | 'F' | 'X';
41
+
42
+ export const TERMINAL_TURN_STATUSES: ReadonlySet<string> = new Set<TurnStatus>(['C', 'F', 'X']);
43
+
44
+ /** One turn of the signed-in person's history; cost fields are absent while nothing is settled. */
45
+ export interface ConversationTurn {
46
+ request_id: string;
47
+ conversation_id: string;
48
+ agent_name: string;
49
+ release_version: string;
50
+ status: TurnStatus;
51
+ work_ref?: string;
52
+ task_kind: string;
53
+ input_summary: string;
54
+ error_text?: string;
55
+ /** The product's result card, handed to the kind's `sailTurnResult` template. */
56
+ result?: unknown;
57
+ created_at: string;
58
+ completed_at?: string;
59
+ cost_minor?: number;
60
+ currency?: string;
61
+ exponent?: number;
62
+ }
63
+
64
+ /** Newest first; `next_before` is absent on the last page. */
65
+ export interface ConversationTurnPage {
66
+ turns: ConversationTurn[];
67
+ next_before?: string;
68
+ }
69
+
31
70
  /** Where the app mounts Scout's ConversationHandler, e.g. '/api/wingmate/'. */
32
71
  export const CONVERSATION_BASE_PATH = new InjectionToken<string>('SailConversationBasePath', {
33
72
  providedIn: 'root',
@@ -1,6 +1,5 @@
1
- import { ObjectRef } from './turn_event';
2
-
3
- export type DecisionOutcome = 'allow' | 'deny' | 'pending';
1
+ /** A allowed, D denied, P pending (`audit_decision_outcome`). */
2
+ export type DecisionOutcome = 'A' | 'D' | 'P';
4
3
 
5
4
  export interface PrincipalRef {
6
5
  kind: string;
@@ -13,12 +12,9 @@ export interface AuthorityRef {
13
12
  grantor?: PrincipalRef;
14
13
  }
15
14
 
16
- /**
17
- * One audit record as sail renders it. The audit endpoint is the app's, so the app maps its
18
- * response onto this shape; `id` is whatever key the endpoint gives a record.
19
- */
15
+ /** One governed decision of the tenant (Scout `api.DecisionRecord`); evidence is a digest only. */
20
16
  export interface DecisionRecord {
21
- id: string | number;
17
+ id: number;
22
18
  category: string;
23
19
  action: string;
24
20
  resource: string;
@@ -26,17 +22,26 @@ export interface DecisionRecord {
26
22
  reason: string;
27
23
  principal: PrincipalRef;
28
24
  authority?: AuthorityRef;
25
+ /** The client application the principal acted through. */
26
+ client_ref?: string;
27
+ work_ref?: string;
29
28
  scope_id?: string;
30
29
  release_version?: string;
31
30
  policy_id?: string;
32
31
  policy_version?: string;
33
32
  obligations?: string[];
34
- evidence?: ObjectRef;
33
+ evidence_digest?: string;
35
34
  request_id: string;
36
35
  conversation_id?: string;
37
36
  occurred_at: string;
38
37
  }
39
38
 
39
+ /** Newest first; `next_before` is absent on the last page. */
40
+ export interface DecisionPage {
41
+ records: DecisionRecord[];
42
+ next_before?: number;
43
+ }
44
+
40
45
  export interface DecisionCategoryGroup {
41
46
  category: string;
42
47
  records: DecisionRecord[];
@@ -10,6 +10,7 @@ export const TURN_EVENT_KINDS = [
10
10
  'effect',
11
11
  'progress',
12
12
  'result',
13
+ 'citations',
13
14
  'extension',
14
15
  ] as const;
15
16
 
@@ -73,6 +74,14 @@ export interface TurnExtensionEvent {
73
74
  payload?: unknown;
74
75
  }
75
76
 
77
+ export interface Citation {
78
+ url: string;
79
+ domain?: string;
80
+ title?: string;
81
+ snippet?: string;
82
+ position: number;
83
+ }
84
+
76
85
  export interface TurnEvent {
77
86
  v: number;
78
87
  kind: TurnEventKind;
@@ -83,6 +92,7 @@ export interface TurnEvent {
83
92
  effect?: TurnEffectEvent;
84
93
  progress?: TurnProgressEvent;
85
94
  extension?: TurnExtensionEvent;
95
+ citations?: Citation[];
86
96
  }
87
97
 
88
98
  const knownKinds: ReadonlySet<string> = new Set(TURN_EVENT_KINDS);
@@ -2,27 +2,42 @@ import { Injectable } from '@angular/core';
2
2
  import { Observable } from 'rxjs';
3
3
  import { BaseRestService } from '../../service/base_rest.service';
4
4
  import {
5
+ AgentArchiveRequest,
6
+ AgentGuardrailChange,
7
+ AgentGuardrails,
5
8
  AgentAuditEvent,
9
+ AgentCreateRequest,
6
10
  AgentDraft,
7
11
  AgentEnabledState,
8
12
  AgentPublishRequest,
9
13
  AgentRelease,
10
14
  AgentReleaseSection,
15
+ AgentRenameRequest,
11
16
  AgentResetRequest,
12
17
  AgentRestoreRequest,
13
18
  AgentSetDefaultRequest,
14
19
  AgentSetEnabledRequest,
20
+ AgentSettingChange,
21
+ AgentSettings,
15
22
  AgentSummary,
16
23
  AgentTestRequest,
17
24
  AgentTestResult,
25
+ SafetyEvent,
26
+ StudioFeature,
18
27
  StudioModel,
19
28
  } from '../model/agent_studio';
29
+ import { DecisionOutcome, DecisionPage } from '../model/decision';
20
30
 
21
31
  const studioBasePath = '/api/agent-studio/';
22
32
 
23
- /** Client for the `studio-v2` Agent Studio routes, which are fixed and unversioned. */
33
+ /** Client for Scout's core Agent Studio routes, which are fixed and unversioned. */
24
34
  @Injectable({ providedIn: 'root' })
25
35
  export class AgentStudioService extends BaseRestService {
36
+ /** The optional Studio APIs the backend mounted; `agents` is the core set. */
37
+ features(): Observable<StudioFeature[]> {
38
+ return this.http.get<StudioFeature[]>(this.studioUrl('features'));
39
+ }
40
+
26
41
  listAgents(): Observable<AgentSummary[]> {
27
42
  return this.http.get<AgentSummary[]>(this.studioUrl('agents'));
28
43
  }
@@ -36,6 +51,30 @@ export class AgentStudioService extends BaseRestService {
36
51
  return this.http.post<AgentDraft>(this.studioUrl('draft'), draft);
37
52
  }
38
53
 
54
+ create(request: AgentCreateRequest): Observable<AgentSummary> {
55
+ return this.http.post<AgentSummary>(this.studioUrl('create'), request);
56
+ }
57
+
58
+ rename(request: AgentRenameRequest): Observable<AgentSummary> {
59
+ return this.http.post<AgentSummary>(this.studioUrl('rename'), request);
60
+ }
61
+
62
+ archive(request: AgentArchiveRequest): Observable<AgentSummary> {
63
+ return this.http.post<AgentSummary>(this.studioUrl('archive'), request);
64
+ }
65
+
66
+ unarchive(request: AgentArchiveRequest): Observable<AgentSummary> {
67
+ return this.http.post<AgentSummary>(this.studioUrl('unarchive'), request);
68
+ }
69
+
70
+ settings(agentName: string): Observable<AgentSettings> {
71
+ return this.http.get<AgentSettings>(this.studioUrl('settings'), { params: { agent_name: agentName } });
72
+ }
73
+
74
+ saveSetting(change: AgentSettingChange): Observable<AgentSettings> {
75
+ return this.http.post<AgentSettings>(this.studioUrl('setting'), change);
76
+ }
77
+
39
78
  setEnabled(request: AgentSetEnabledRequest): Observable<AgentEnabledState> {
40
79
  return this.http.post<AgentEnabledState>(this.studioUrl('enabled'), request);
41
80
  }
@@ -79,6 +118,31 @@ export class AgentStudioService extends BaseRestService {
79
118
  return this.http.get<StudioModel[]>(this.studioUrl('models'));
80
119
  }
81
120
 
121
+ /** Scout's optional `decisions` feature: the tenant's decision records, newest first. */
122
+ decisions(before = 0, limit = 0, category = '', outcome?: DecisionOutcome): Observable<DecisionPage> {
123
+ const params: Record<string, string> = {};
124
+ if (before) params['before'] = String(before);
125
+ if (limit) params['limit'] = String(limit);
126
+ if (category) params['category'] = category;
127
+ if (outcome) params['outcome'] = outcome;
128
+ return this.http.get<DecisionPage>(this.studioUrl('decisions'), { params });
129
+ }
130
+
131
+ /** Scout's optional `guardrails` feature. */
132
+ guardrails(agentName: string): Observable<AgentGuardrails> {
133
+ return this.http.get<AgentGuardrails>(this.studioUrl('guardrails'), { params: { agent_name: agentName } });
134
+ }
135
+
136
+ saveGuardrails(change: AgentGuardrailChange): Observable<AgentGuardrails> {
137
+ return this.http.post<AgentGuardrails>(this.studioUrl('guardrails'), change);
138
+ }
139
+
140
+ /** Newest first; pass the last id as `before` for the next page. */
141
+ safetyEvents(agentName: string, before = 0, limit = 50): Observable<SafetyEvent[]> {
142
+ return this.http.get<SafetyEvent[]>(this.studioUrl('safety-events'),
143
+ { params: { agent_name: agentName, before: String(before), limit: String(limit) } });
144
+ }
145
+
82
146
  private studioUrl(route: string): string {
83
147
  return this.url(studioBasePath + route);
84
148
  }
@@ -0,0 +1,38 @@
1
+ import { Injectable } from '@angular/core';
2
+ import { Observable } from 'rxjs';
3
+ import { BaseRestService } from '../../service/base_rest.service';
4
+ import { ApprovalRequest, ApprovalResolveRequest, Confirmation, ConfirmationKey } from '../model/approval';
5
+
6
+ const base = '/api/approval/';
7
+
8
+ /** Client for Scout's approval desk and MCP confirmation inbox under the fixed `/api/approval/` path. */
9
+ @Injectable({ providedIn: 'root' })
10
+ export class ApprovalService extends BaseRestService {
11
+ /** The requests the signed-in person may decide now, soonest deadline first. */
12
+ pending(): Observable<ApprovalRequest[]> {
13
+ return this.http.get<ApprovalRequest[]>(this.url(base + 'pending'));
14
+ }
15
+
16
+ /** 409 when the request was reassigned or decided differently since it was read. */
17
+ resolve(request: ApprovalResolveRequest): Observable<ApprovalRequest> {
18
+ return this.http.post<ApprovalRequest>(this.url(base + 'resolve'), request);
19
+ }
20
+
21
+ /** Open confirmations and those created since `since` (RFC 3339), across the person's tenants. */
22
+ confirmations(since = ''): Observable<Confirmation[]> {
23
+ return this.http.get<Confirmation[]>(this.url(base + 'confirmations'), { params: since ? { since } : {} });
24
+ }
25
+
26
+ decideConfirmation(key: ConfirmationKey, approve: boolean, note = ''): Observable<Confirmation> {
27
+ return this.http.post<Confirmation>(this.url(base + 'confirmation/decide'), { ...key, approve, note });
28
+ }
29
+
30
+ withdrawConfirmation(key: ConfirmationKey): Observable<ConfirmationKey> {
31
+ return this.http.post<ConfirmationKey>(this.url(base + 'confirmation/withdraw'), key);
32
+ }
33
+
34
+ /** Records the verified outcome of an unknown confirmation: D executed or F failed. */
35
+ reconcileConfirmation(key: ConfirmationKey, outcome: 'D' | 'F', note = ''): Observable<Confirmation> {
36
+ return this.http.post<Confirmation>(this.url(base + 'confirmation/reconcile'), { ...key, outcome, note });
37
+ }
38
+ }
@@ -7,6 +7,7 @@ import {
7
7
  ConversationStreamError,
8
8
  ConversationStreamFailure,
9
9
  ConversationTurnAccepted,
10
+ ConversationTurnPage,
10
11
  ConversationTurnRequest,
11
12
  } from '../model/conversation';
12
13
  import { TurnReplyFrame } from '../model/reply_frame';
@@ -34,6 +35,15 @@ export class ConversationService extends BaseRestService {
34
35
  return this.http.post<ConversationTurnAccepted>(this.conversationUrl('turn'), request);
35
36
  }
36
37
 
38
+ /** The person's turns, newest first; `before` continues from a page's `next_before`. */
39
+ turns(workRef = '', before = '', limit = 0): Observable<ConversationTurnPage> {
40
+ const params: Record<string, string> = {};
41
+ if (workRef) params['work_ref'] = workRef;
42
+ if (before) params['before'] = before;
43
+ if (limit) params['limit'] = String(limit);
44
+ return this.http.get<ConversationTurnPage>(this.conversationUrl('turns'), { params });
45
+ }
46
+
37
47
  /** Cancel is a request: the turn stays open until its final frame arrives. */
38
48
  cancel(requestId: string, reason?: string): Observable<ConversationTurnAccepted> {
39
49
  return this.http.post<ConversationTurnAccepted>(this.conversationUrl('turn/cancel'), {
@@ -0,0 +1,33 @@
1
+ import { AgentReleaseSection } from '../model/agent_studio';
2
+
3
+ export interface ReleaseSectionDiff {
4
+ language_code: string;
5
+ prompt_header_id: number;
6
+ caption: string;
7
+ status: 'added' | 'changed' | 'removed';
8
+ old_text: string;
9
+ new_text: string;
10
+ }
11
+
12
+ /**
13
+ * Section changes from `prior` to `current`, keyed by language and prompt header. Caption and
14
+ * description are part of the compiled prompt, so they count; so does a pure reordering.
15
+ */
16
+ export function diffReleaseSections(prior: AgentReleaseSection[], current: AgentReleaseSection[]): ReleaseSectionDiff[] {
17
+ const key = (s: AgentReleaseSection) => `${s.language_code}#${s.prompt_header_id}`;
18
+ const text = (s: AgentReleaseSection) =>
19
+ `${s.description ? `${s.caption} — ${s.description}` : s.caption}\n${s.output ? `${s.instruction}\n\nOutput: ${s.output}` : s.instruction}`;
20
+ const row = (s: AgentReleaseSection, status: ReleaseSectionDiff['status'], old_text: string, new_text: string): ReleaseSectionDiff =>
21
+ ({ language_code: s.language_code, prompt_header_id: s.prompt_header_id, caption: s.caption, status, old_text, new_text });
22
+ const remaining = new Map(prior.map((s) => [key(s), s]));
23
+ const rows: ReleaseSectionDiff[] = [];
24
+ for (const section of current) {
25
+ const old = remaining.get(key(section));
26
+ remaining.delete(key(section));
27
+ if (!old) rows.push(row(section, 'added', '', text(section)));
28
+ else if (text(old) !== text(section)) rows.push(row(section, 'changed', text(old), text(section)));
29
+ else if (old.sequence !== section.sequence) rows.push(row(section, 'changed', `position ${old.sequence}`, `position ${section.sequence}`));
30
+ }
31
+ for (const old of remaining.values()) rows.push(row(old, 'removed', text(old), ''));
32
+ return rows;
33
+ }