@nauticana/sail 1.1.33 → 1.1.34
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/README.md +17 -11
- package/package.json +1 -1
- package/src/agent/component/approval_inbox.html +63 -0
- package/src/agent/component/approval_inbox.ts +91 -0
- package/src/agent/component/decision_timeline.html +4 -4
- package/src/agent/component/decision_timeline.ts +2 -0
- package/src/agent/component/prompt_section_editor.html +4 -3
- package/src/agent/component/prompt_section_editor.ts +12 -5
- package/src/agent/component/release_sections.html +1 -1
- package/src/agent/component/release_sections.ts +2 -1
- package/src/agent/component/studio_page.html +269 -0
- package/src/agent/component/studio_page.ts +346 -0
- package/src/agent/component/studio_tab.ts +14 -0
- package/src/agent/component/turn_event_list.html +9 -0
- package/src/agent/index.ts +6 -0
- package/src/agent/model/agent_studio.ts +122 -9
- package/src/agent/model/approval.ts +42 -0
- package/src/agent/model/conversation.ts +9 -1
- package/src/agent/model/decision.ts +2 -1
- package/src/agent/model/turn_event.ts +10 -0
- package/src/agent/service/agent_studio.service.ts +36 -1
- package/src/agent/service/approval.service.ts +18 -0
- package/src/agent/util/release_diff.ts +33 -0
- package/src/agent/util/studio_errors.ts +11 -0
- package/src/agent/util/turn_timeline.ts +7 -1
- package/src/component/login/account_deletion.html +17 -4
- package/src/component/login/account_deletion.ts +35 -28
- package/src/component/login/social_login.ts +6 -5
- package/src/index.ts +1 -0
- package/src/model/auth.ts +17 -2
- package/src/service/auth.service.ts +10 -4
- package/src/service/caption_pipe.ts +26 -0
- package/src/service/label.service.ts +5 -4
- package/src/service/rest_url.ts +1 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
/** Agent Studio
|
|
1
|
+
/** Agent Studio wire types (Scout v1.2.7). Field names are the backend's JSON names. */
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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,89 @@ 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
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** Scout approval desk wire types (v1.2.7). Codes render through backend captions. */
|
|
2
|
+
|
|
3
|
+
/** A approved, X rejected, E edited (tool-call proposals only, with revised arguments). */
|
|
4
|
+
export type ApprovalVerdict = 'A' | 'X' | 'E';
|
|
5
|
+
|
|
6
|
+
export interface ApproverReference {
|
|
7
|
+
kind: string;
|
|
8
|
+
ref: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** One decision a reviewer owes; evidence stays behind its own authorization. */
|
|
12
|
+
export interface ApprovalRequest {
|
|
13
|
+
request_id: string;
|
|
14
|
+
execution_step_id: number;
|
|
15
|
+
conversation_id: string;
|
|
16
|
+
scope_id?: string;
|
|
17
|
+
rule_id: string;
|
|
18
|
+
action: string;
|
|
19
|
+
resource: string;
|
|
20
|
+
output_class: string;
|
|
21
|
+
risk_tier: string;
|
|
22
|
+
summary: string;
|
|
23
|
+
proposed_digest: string;
|
|
24
|
+
status: string;
|
|
25
|
+
deadline_at?: string;
|
|
26
|
+
created_at: string;
|
|
27
|
+
revises_step_id?: number;
|
|
28
|
+
rule_source: string;
|
|
29
|
+
route_id?: string;
|
|
30
|
+
route_step: number;
|
|
31
|
+
approver_reference?: ApproverReference;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ApprovalResolveRequest {
|
|
35
|
+
request_id: string;
|
|
36
|
+
execution_step_id: number;
|
|
37
|
+
verdict: ApprovalVerdict;
|
|
38
|
+
proposed_digest: string;
|
|
39
|
+
reason?: string;
|
|
40
|
+
/** The JSON object an `E` verdict runs the tool call with instead. */
|
|
41
|
+
revised_arguments?: Record<string, unknown>;
|
|
42
|
+
}
|
|
@@ -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
|
-
|
|
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
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ObjectRef } from './turn_event';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/** A allowed, D denied, P pending (`audit_decision_outcome`). */
|
|
4
|
+
export type DecisionOutcome = 'A' | 'D' | 'P';
|
|
4
5
|
|
|
5
6
|
export interface PrincipalRef {
|
|
6
7
|
kind: string;
|
|
@@ -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,38 @@ 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,
|
|
5
6
|
AgentAuditEvent,
|
|
7
|
+
AgentCreateRequest,
|
|
6
8
|
AgentDraft,
|
|
7
9
|
AgentEnabledState,
|
|
8
10
|
AgentPublishRequest,
|
|
9
11
|
AgentRelease,
|
|
10
12
|
AgentReleaseSection,
|
|
13
|
+
AgentRenameRequest,
|
|
11
14
|
AgentResetRequest,
|
|
12
15
|
AgentRestoreRequest,
|
|
13
16
|
AgentSetDefaultRequest,
|
|
14
17
|
AgentSetEnabledRequest,
|
|
18
|
+
AgentSettingChange,
|
|
19
|
+
AgentSettings,
|
|
15
20
|
AgentSummary,
|
|
16
21
|
AgentTestRequest,
|
|
17
22
|
AgentTestResult,
|
|
23
|
+
StudioFeature,
|
|
18
24
|
StudioModel,
|
|
19
25
|
} from '../model/agent_studio';
|
|
20
26
|
|
|
21
27
|
const studioBasePath = '/api/agent-studio/';
|
|
22
28
|
|
|
23
|
-
/** Client for
|
|
29
|
+
/** Client for Scout's core Agent Studio routes, which are fixed and unversioned. */
|
|
24
30
|
@Injectable({ providedIn: 'root' })
|
|
25
31
|
export class AgentStudioService extends BaseRestService {
|
|
32
|
+
/** The optional Studio APIs the backend mounted; `agents` is the core set. */
|
|
33
|
+
features(): Observable<StudioFeature[]> {
|
|
34
|
+
return this.http.get<StudioFeature[]>(this.studioUrl('features'));
|
|
35
|
+
}
|
|
36
|
+
|
|
26
37
|
listAgents(): Observable<AgentSummary[]> {
|
|
27
38
|
return this.http.get<AgentSummary[]>(this.studioUrl('agents'));
|
|
28
39
|
}
|
|
@@ -36,6 +47,30 @@ export class AgentStudioService extends BaseRestService {
|
|
|
36
47
|
return this.http.post<AgentDraft>(this.studioUrl('draft'), draft);
|
|
37
48
|
}
|
|
38
49
|
|
|
50
|
+
create(request: AgentCreateRequest): Observable<AgentSummary> {
|
|
51
|
+
return this.http.post<AgentSummary>(this.studioUrl('create'), request);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
rename(request: AgentRenameRequest): Observable<AgentSummary> {
|
|
55
|
+
return this.http.post<AgentSummary>(this.studioUrl('rename'), request);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
archive(request: AgentArchiveRequest): Observable<AgentSummary> {
|
|
59
|
+
return this.http.post<AgentSummary>(this.studioUrl('archive'), request);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
unarchive(request: AgentArchiveRequest): Observable<AgentSummary> {
|
|
63
|
+
return this.http.post<AgentSummary>(this.studioUrl('unarchive'), request);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
settings(agentName: string): Observable<AgentSettings> {
|
|
67
|
+
return this.http.get<AgentSettings>(this.studioUrl('settings'), { params: { agent_name: agentName } });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
saveSetting(change: AgentSettingChange): Observable<AgentSettings> {
|
|
71
|
+
return this.http.post<AgentSettings>(this.studioUrl('setting'), change);
|
|
72
|
+
}
|
|
73
|
+
|
|
39
74
|
setEnabled(request: AgentSetEnabledRequest): Observable<AgentEnabledState> {
|
|
40
75
|
return this.http.post<AgentEnabledState>(this.studioUrl('enabled'), request);
|
|
41
76
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Injectable } from '@angular/core';
|
|
2
|
+
import { Observable } from 'rxjs';
|
|
3
|
+
import { BaseRestService } from '../../service/base_rest.service';
|
|
4
|
+
import { ApprovalRequest, ApprovalResolveRequest } from '../model/approval';
|
|
5
|
+
|
|
6
|
+
/** Client for Scout's approval desk under the fixed `/api/approval/` path. */
|
|
7
|
+
@Injectable({ providedIn: 'root' })
|
|
8
|
+
export class ApprovalService extends BaseRestService {
|
|
9
|
+
/** The requests the signed-in person may decide now, soonest deadline first. */
|
|
10
|
+
pending(): Observable<ApprovalRequest[]> {
|
|
11
|
+
return this.http.get<ApprovalRequest[]>(this.url('/api/approval/pending'));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** 409 when the request was reassigned or decided differently since it was read. */
|
|
15
|
+
resolve(request: ApprovalResolveRequest): Observable<ApprovalRequest> {
|
|
16
|
+
return this.http.post<ApprovalRequest>(this.url('/api/approval/resolve'), request);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { errorDetail } from '../../util/errors';
|
|
1
2
|
import { AgentFieldError, AgentValidationProblem } from '../model/agent_studio';
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -59,3 +60,13 @@ export function retryAfterSeconds(err: unknown): number | undefined {
|
|
|
59
60
|
const seconds = Number(raw);
|
|
60
61
|
return Number.isFinite(seconds) && seconds >= 0 ? seconds : undefined;
|
|
61
62
|
}
|
|
63
|
+
|
|
64
|
+
/** One message for a failed Studio call: the conflict cause, the field errors, or the detail. */
|
|
65
|
+
export function studioErrorMessage(err: unknown, fallback = 'Request failed.'): string {
|
|
66
|
+
const cause = studioConflictCause(err);
|
|
67
|
+
if (cause === 'revision') return 'This agent changed since you opened it. Reload and reapply your edits.';
|
|
68
|
+
if (cause === 'sealed') return 'A wider scope sealed this section, so narrower layers cannot change it.';
|
|
69
|
+
const fields = studioFieldErrors(err);
|
|
70
|
+
if (fields.length > 0) return fields.map((f) => `${f.field}: ${f.message}`).join('; ');
|
|
71
|
+
return errorDetail(err, fallback);
|
|
72
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
Citation,
|
|
2
3
|
EvidenceRef,
|
|
3
4
|
TurnApprovalEvent,
|
|
4
5
|
TurnEffectEvent,
|
|
@@ -31,6 +32,7 @@ export type TurnTimelineItem =
|
|
|
31
32
|
| { kind: 'evidence'; refs: EvidenceRef[] }
|
|
32
33
|
| { kind: 'progress'; progress: TurnProgressEvent }
|
|
33
34
|
| { kind: 'extension'; extension: TurnExtensionEvent }
|
|
35
|
+
| { kind: 'citations'; citations: Citation[] }
|
|
34
36
|
| { kind: 'result' };
|
|
35
37
|
|
|
36
38
|
function errorClassOf(tool: TurnToolEvent): string | undefined {
|
|
@@ -62,7 +64,8 @@ export function buildTurnTimeline(events: readonly unknown[]): TurnTimelineItem[
|
|
|
62
64
|
};
|
|
63
65
|
|
|
64
66
|
const applyApproval = (approval: TurnApprovalEvent | undefined, state: ToolCallApproval): void => {
|
|
65
|
-
|
|
67
|
+
// A turn-level suspension carries an empty approval; turn status shows it, not a tool row.
|
|
68
|
+
if (!approval?.call_id) return;
|
|
66
69
|
const call = callFor(approval);
|
|
67
70
|
call.approval = state;
|
|
68
71
|
call.decision = approval.decision;
|
|
@@ -106,6 +109,9 @@ export function buildTurnTimeline(events: readonly unknown[]): TurnTimelineItem[
|
|
|
106
109
|
case 'extension':
|
|
107
110
|
if (event.extension) items.push({ kind: 'extension', extension: event.extension });
|
|
108
111
|
break;
|
|
112
|
+
case 'citations':
|
|
113
|
+
if (event.citations?.length) items.push({ kind: 'citations', citations: event.citations });
|
|
114
|
+
break;
|
|
109
115
|
case 'result':
|
|
110
116
|
items.push({ kind: 'result' });
|
|
111
117
|
break;
|
|
@@ -13,10 +13,23 @@
|
|
|
13
13
|
<input matInput formControlName="confirm" autocomplete="off" required>
|
|
14
14
|
</mat-form-field>
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
<mat-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
@if (method() === 'password') {
|
|
17
|
+
<mat-form-field appearance="outline">
|
|
18
|
+
<mat-label>Confirm your password</mat-label>
|
|
19
|
+
<input matInput type="password" formControlName="credential" autocomplete="current-password" required>
|
|
20
|
+
</mat-form-field>
|
|
21
|
+
} @else {
|
|
22
|
+
<button matButton type="button" [disabled]="loading()" (click)="sendCode()">
|
|
23
|
+
{{ codeSent() ? 'Resend code' : 'Send me a code' }}
|
|
24
|
+
</button>
|
|
25
|
+
<mat-form-field appearance="outline">
|
|
26
|
+
<mat-label>One-time code</mat-label>
|
|
27
|
+
<input matInput formControlName="credential" inputmode="numeric" autocomplete="one-time-code" required>
|
|
28
|
+
</mat-form-field>
|
|
29
|
+
}
|
|
30
|
+
<button matButton type="button" (click)="toggleMethod()">
|
|
31
|
+
{{ method() === 'password' ? 'Use a one-time code instead' : 'Use my password instead' }}
|
|
32
|
+
</button>
|
|
20
33
|
|
|
21
34
|
<mat-form-field appearance="outline">
|
|
22
35
|
<mat-label>Reason (optional)</mat-label>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
ChangeDetectionStrategy, Component, ViewEncapsulation, computed, inject, input, signal,
|
|
2
|
+
ChangeDetectionStrategy, Component, ViewEncapsulation, computed, inject, input, linkedSignal, signal,
|
|
3
3
|
} from '@angular/core';
|
|
4
|
-
import {
|
|
4
|
+
import { toSignal } from '@angular/core/rxjs-interop';
|
|
5
5
|
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
|
|
6
6
|
import { MatButtonModule } from '@angular/material/button';
|
|
7
7
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
@@ -10,18 +10,17 @@ import { Router, RouterLink } from '@angular/router';
|
|
|
10
10
|
import { BaseAsync } from '../abstract/base_async';
|
|
11
11
|
import { BaseAuthService } from '../../service/auth.service';
|
|
12
12
|
import { SAIL_GUI_CONFIG, SailGuiConfig, DEFAULT_CONFIG } from '../../config';
|
|
13
|
+
import { ReauthCredentials } from '../../model/auth';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* AccountDeletionComponent — App Store / Play Store compliance surface.
|
|
16
17
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* DELETE /api/user/account for
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* component then navigates to `config.accountDeletedRoute` (default
|
|
24
|
-
* `/login/local`).
|
|
18
|
+
* The destructive button enables once the user types `confirmationText`
|
|
19
|
+
* (default "DELETE") and re-authenticates with a password or, for accounts
|
|
20
|
+
* without one, a one-time code (`reauthMethod`, switchable in the form).
|
|
21
|
+
* The optional reason is forwarded to DELETE /api/user/account for audit.
|
|
22
|
+
* On success the session is cleared and the router goes to
|
|
23
|
+
* `config.accountDeletedRoute` (default `/login/local`).
|
|
25
24
|
*/
|
|
26
25
|
@Component({
|
|
27
26
|
selector: 'sail-account-deletion',
|
|
@@ -44,37 +43,45 @@ export class AccountDeletionComponent extends BaseAsync {
|
|
|
44
43
|
inject(SAIL_GUI_CONFIG, { optional: true }) ?? DEFAULT_CONFIG;
|
|
45
44
|
|
|
46
45
|
readonly confirmationText = input('DELETE');
|
|
46
|
+
readonly reauthMethod = input<'password' | 'code'>('password');
|
|
47
|
+
readonly method = linkedSignal(() => this.reauthMethod());
|
|
48
|
+
readonly codeSent = signal(false);
|
|
47
49
|
|
|
48
50
|
readonly form = this.fb.nonNullable.group({
|
|
49
|
-
confirm:
|
|
50
|
-
|
|
51
|
-
reason:
|
|
51
|
+
confirm: ['', Validators.required],
|
|
52
|
+
credential: ['', Validators.required],
|
|
53
|
+
reason: [''],
|
|
52
54
|
});
|
|
53
55
|
|
|
54
|
-
readonly
|
|
55
|
-
private readonly
|
|
56
|
+
private readonly confirmValue = toSignal(this.form.controls.confirm.valueChanges, { initialValue: '' });
|
|
57
|
+
private readonly credentialValue = toSignal(this.form.controls.credential.valueChanges, { initialValue: '' });
|
|
56
58
|
readonly canSubmit = computed(() =>
|
|
57
|
-
this.
|
|
58
|
-
!!this.
|
|
59
|
+
this.confirmValue() === this.confirmationText() &&
|
|
60
|
+
!!this.credentialValue() &&
|
|
59
61
|
!this.loading(),
|
|
60
62
|
);
|
|
61
63
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
this.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
64
|
+
toggleMethod() {
|
|
65
|
+
this.method.update((m) => m === 'password' ? 'code' : 'password');
|
|
66
|
+
this.codeSent.set(false);
|
|
67
|
+
this.form.controls.credential.reset();
|
|
68
|
+
this.clearMessages();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
sendCode() {
|
|
72
|
+
this.run(this.auth.sendReauthCode(), (res) => {
|
|
73
|
+
this.codeSent.set(true);
|
|
74
|
+
this.successMessage.set(`A code was sent to your ${res.channel}.`);
|
|
75
|
+
}, 'Failed to send a code. Please try again.');
|
|
70
76
|
}
|
|
71
77
|
|
|
72
78
|
onDelete() {
|
|
73
79
|
if (!this.canSubmit()) return;
|
|
74
|
-
const
|
|
75
|
-
const
|
|
80
|
+
const credential = this.form.controls.credential.value;
|
|
81
|
+
const reauth: ReauthCredentials = this.method() === 'code' ? { reauthCode: credential } : { password: credential };
|
|
82
|
+
const reason = this.form.controls.reason.value.trim() || undefined;
|
|
76
83
|
this.run(
|
|
77
|
-
this.auth.deleteAccount(
|
|
84
|
+
this.auth.deleteAccount(reauth, reason),
|
|
78
85
|
() => this.router.navigateByUrl(this.guiConfig.accountDeletedRoute ?? '/login/local'),
|
|
79
86
|
'Failed to delete account. Please try again.',
|
|
80
87
|
);
|
|
@@ -7,7 +7,7 @@ import { BaseAuthService } from '../../service/auth.service';
|
|
|
7
7
|
import { loadScript } from '../../service/script_loader';
|
|
8
8
|
import { SAIL_GUI_CONFIG, SailGuiConfig, DEFAULT_CONFIG } from '../../config';
|
|
9
9
|
import {
|
|
10
|
-
ConsentState, LoginResponseSocial, SignupConsent, SocialProvider,
|
|
10
|
+
ConsentState, LoginResponseSocial, SignupConsent, SocialName, SocialProvider,
|
|
11
11
|
} from '../../model/auth';
|
|
12
12
|
import { firstValueFrom } from 'rxjs';
|
|
13
13
|
|
|
@@ -22,7 +22,8 @@ const APPLE_URL = 'https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/
|
|
|
22
22
|
* buttons using each provider's official SDK.
|
|
23
23
|
*
|
|
24
24
|
* Flow: click → SDK returns ID token → BaseAuthService.loginSocial(provider,
|
|
25
|
-
* idToken, signupConsent) → emits loginSuccess / loginError.
|
|
25
|
+
* idToken, signupConsent) → emits loginSuccess / loginError. Apple's
|
|
26
|
+
* first-authorization name rides along as loginSuccess.socialName.
|
|
26
27
|
*
|
|
27
28
|
* Requires `googleClientId` / `appleServiceId` in SailGuiConfig; providers
|
|
28
29
|
* without config are hidden (with a console.warn for diagnostics).
|
|
@@ -104,17 +105,17 @@ export class SocialLoginComponent extends BaseAsync implements OnInit {
|
|
|
104
105
|
try {
|
|
105
106
|
const res = await AppleID.auth.signIn();
|
|
106
107
|
const token = res?.authorization?.id_token;
|
|
107
|
-
if (token) this.onIdToken('apple', token);
|
|
108
|
+
if (token) this.onIdToken('apple', token, res.user?.name);
|
|
108
109
|
} catch (err: unknown) {
|
|
109
110
|
this.loginError.emit(err instanceof Error ? err : new Error(String(err)));
|
|
110
111
|
}
|
|
111
112
|
}
|
|
112
113
|
|
|
113
|
-
private onIdToken(provider: SocialProvider, idToken: string) {
|
|
114
|
+
private onIdToken(provider: SocialProvider, idToken: string, socialName?: SocialName) {
|
|
114
115
|
const fallback = `Sign-in with ${provider} failed.`;
|
|
115
116
|
this.run(
|
|
116
117
|
this.auth.loginSocial(provider, idToken, this.buildSignupConsent()),
|
|
117
|
-
(resp) => this.loginSuccess.emit(resp),
|
|
118
|
+
(resp) => this.loginSuccess.emit(socialName ? { ...resp, socialName } : resp),
|
|
118
119
|
fallback,
|
|
119
120
|
(err) => this.loginError.emit(this.toError(err, fallback)),
|
|
120
121
|
);
|
package/src/index.ts
CHANGED
|
@@ -94,6 +94,7 @@ export * from './service/api-response.interceptor';
|
|
|
94
94
|
export * from './service/rest_service';
|
|
95
95
|
export * from './service/rest_url';
|
|
96
96
|
export * from './service/label.service';
|
|
97
|
+
export * from './service/caption_pipe';
|
|
97
98
|
export * from './service/base_rest.service';
|
|
98
99
|
export * from './util/text';
|
|
99
100
|
export * from './util/money';
|