@north-light/sdk 0.3.514 → 0.3.518
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/index.d.ts +1 -1
- package/dist/resources/questions.d.ts +12 -1
- package/dist/resources/questions.js +7 -1
- package/dist/resources/runs.d.ts +2 -0
- package/dist/skill/northlight/SKILL.md +2 -2
- package/dist/testing/cloud.d.ts +1 -1
- package/dist/testing/cloud.js +10 -1
- package/dist/testing/run.d.ts +7 -0
- package/dist/testing/run.js +16 -4
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export type { AttachmentLink } from './resources/attachments.js';
|
|
|
14
14
|
export type { Share, ShareCreated, ShareCreateParams } from './resources/shares.js';
|
|
15
15
|
export type { Doc, DocEditParams, DocWriteParams } from './resources/docs.js';
|
|
16
16
|
export type { MemoryDocument, MemoryGetParams, MemoryListItem, MemoryListPage, MemoryListParams } from './resources/memory.js';
|
|
17
|
-
export type { Question, QuestionAnswered, QuestionFields, QuestionResponses, QuestionSlot, QuestionStatus } from './resources/questions.js';
|
|
17
|
+
export type { Question, QuestionAnswered, QuestionCanceled, QuestionFields, QuestionKind, QuestionResponses, QuestionSlot, QuestionStatus } from './resources/questions.js';
|
|
18
18
|
export type { ProviderToolEntry } from './resources/providers.js';
|
|
19
19
|
export { OAuth2Client, ConnectedCrouter, DEFAULT_DIRECTORY_TIMEOUT_MS, MemoryConnectionStore, profileFromClaims } from './oauth/index.js';
|
|
20
20
|
export { authorizationRefusalOf, refusalOf } from './oauth/index.js';
|
|
@@ -20,6 +20,8 @@ export interface QuestionFields {
|
|
|
20
20
|
slots: QuestionSlot[];
|
|
21
21
|
}
|
|
22
22
|
export type QuestionStatus = 'open' | 'answered' | 'canceled';
|
|
23
|
+
/** A `decision` asks the person for a response; a `notice` asks for nothing and is acknowledged with `answer(id, {})`. */
|
|
24
|
+
export type QuestionKind = 'decision' | 'notice';
|
|
23
25
|
/** Responses keyed by slot id. */
|
|
24
26
|
export type QuestionResponses = Record<string, unknown>;
|
|
25
27
|
/** A question an agent in a run asked the user. */
|
|
@@ -27,10 +29,15 @@ export interface Question extends QuestionFields {
|
|
|
27
29
|
run_id: string;
|
|
28
30
|
/** The asking node. */
|
|
29
31
|
node_id: string;
|
|
32
|
+
kind: QuestionKind;
|
|
30
33
|
status: QuestionStatus;
|
|
31
34
|
/** Present once answered. */
|
|
32
35
|
answer?: QuestionResponses;
|
|
33
36
|
}
|
|
37
|
+
/** `questions.cancel`'s result: the question as `questions.get` returns it, with `status: 'canceled'`. */
|
|
38
|
+
export type QuestionCanceled = Question & {
|
|
39
|
+
status: 'canceled';
|
|
40
|
+
};
|
|
34
41
|
export interface QuestionAnswered {
|
|
35
42
|
question_id: string;
|
|
36
43
|
status: 'answered';
|
|
@@ -42,8 +49,12 @@ export declare class Questions {
|
|
|
42
49
|
constructor(request: Request);
|
|
43
50
|
/** Read a question of a run the caller can read; anything else is `not_found`. */
|
|
44
51
|
get(questionId: string, options?: RequestOptions): Promise<Question>;
|
|
45
|
-
/** Answer an open question of the caller's own run
|
|
52
|
+
/** Answer an open question of the caller's own run; a notice is acknowledged with empty `responses`. An answer that does not match a slot is
|
|
46
53
|
* `invalid_request` with `param` naming the slot, and records nothing. A question already answered or
|
|
47
54
|
* canceled — including the loser of a race — is 409 `run_settled` with `details.state` and `details.result`. */
|
|
48
55
|
answer(questionId: string, responses: QuestionResponses, options?: RequestOptions): Promise<QuestionAnswered>;
|
|
56
|
+
/** Cancel an open question of the caller's own run (or the caller's own runless provider approval): the person
|
|
57
|
+
* dismissed it, and the asking agent is told so. A question already answered or canceled is 409 `run_settled`
|
|
58
|
+
* with `details.state` and `details.result`; another app's run is `other_app_run`. A notice is canceled the same way. */
|
|
59
|
+
cancel(questionId: string, options?: RequestOptions): Promise<QuestionCanceled>;
|
|
49
60
|
}
|
|
@@ -9,12 +9,18 @@ export class Questions {
|
|
|
9
9
|
get(questionId, options) {
|
|
10
10
|
return this.request('GET', questionPath(questionId), undefined, options);
|
|
11
11
|
}
|
|
12
|
-
/** Answer an open question of the caller's own run
|
|
12
|
+
/** Answer an open question of the caller's own run; a notice is acknowledged with empty `responses`. An answer that does not match a slot is
|
|
13
13
|
* `invalid_request` with `param` naming the slot, and records nothing. A question already answered or
|
|
14
14
|
* canceled — including the loser of a race — is 409 `run_settled` with `details.state` and `details.result`. */
|
|
15
15
|
answer(questionId, responses, options = {}) {
|
|
16
16
|
return this.request('POST', `${questionPath(questionId)}/answer`, { responses }, { ...options, maxRetries: 0 });
|
|
17
17
|
}
|
|
18
|
+
/** Cancel an open question of the caller's own run (or the caller's own runless provider approval): the person
|
|
19
|
+
* dismissed it, and the asking agent is told so. A question already answered or canceled is 409 `run_settled`
|
|
20
|
+
* with `details.state` and `details.result`; another app's run is `other_app_run`. A notice is canceled the same way. */
|
|
21
|
+
cancel(questionId, options = {}) {
|
|
22
|
+
return this.request('POST', `${questionPath(questionId)}/cancel`, undefined, { ...options, maxRetries: 0 });
|
|
23
|
+
}
|
|
18
24
|
}
|
|
19
25
|
function questionPath(id) {
|
|
20
26
|
return `/v1/questions/${encodeURIComponent(pathSegment(id, 'question id'))}`;
|
package/dist/resources/runs.d.ts
CHANGED
|
@@ -55,6 +55,8 @@ export interface RunObject {
|
|
|
55
55
|
/** Present when the root's latest completed turn ended in error; the next turn that completes without one clears it. */
|
|
56
56
|
last_turn_error?: TurnErrorDTO;
|
|
57
57
|
pending_question_ids: string[];
|
|
58
|
+
/** Open notices (questions of kind `notice`) of the run, oldest first. A notice does not make the run `waiting_on_user`. */
|
|
59
|
+
open_notice_ids: string[];
|
|
58
60
|
usage: {
|
|
59
61
|
input_tokens: number;
|
|
60
62
|
output_tokens: number;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: northlight
|
|
3
3
|
description: "Use when building, debugging or shipping an app that signs people in with Northlight, starts runs on their Northlight runtime, or calls Northlight providers (sms, search, integrations, context, device) with @north-light/sdk."
|
|
4
4
|
metadata:
|
|
5
|
-
sdk-version: "0.3.
|
|
5
|
+
sdk-version: "0.3.518"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Building a Northlight app
|
|
@@ -11,7 +11,7 @@ An app on Northlight signs people in with their Northlight account, then starts
|
|
|
11
11
|
|
|
12
12
|
## First: check this skill matches the SDK
|
|
13
13
|
|
|
14
|
-
This skill is for SDK version `0.3.
|
|
14
|
+
This skill is for SDK version `0.3.518`. If the project has `node_modules/@north-light/sdk/package.json`, compare its `version` with that number. If they differ, run `npx @north-light/sdk skill` in the project, then read the rewritten `.agents/skills/northlight/SKILL.md` (or `.claude/skills/northlight/SKILL.md`) instead of this copy. That command needs no account and no crtr. A project that has no SDK yet starts with `npx @north-light/sdk@latest init <dir>`.
|
|
15
15
|
|
|
16
16
|
## Where things run
|
|
17
17
|
|
package/dist/testing/cloud.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { FakeRun } from './run.js';
|
|
|
6
6
|
import { type FakeRefusalCode, type FakeScope, type FakeUser, type Responder } from './users.js';
|
|
7
7
|
import { type FakeError } from './wire.js';
|
|
8
8
|
/** A runtime operation, named as the daemon's `OPERATIONS` table names it. */
|
|
9
|
-
export type FakeOp = 'grant.get' | 'run.start' | 'run.list' | 'run.get' | 'run.message' | 'run.events' | 'run.interrupt' | 'run.cancel' | 'run.delete' | 'run.rename' | 'question.get' | 'question.answer' | 'trace.get' | 'memory.list' | 'memory.get' | 'provider.tools' | 'provider.call' | 'profile.list';
|
|
9
|
+
export type FakeOp = 'grant.get' | 'run.start' | 'run.list' | 'run.get' | 'run.message' | 'run.events' | 'run.interrupt' | 'run.cancel' | 'run.delete' | 'run.rename' | 'question.get' | 'question.answer' | 'question.cancel' | 'trace.get' | 'memory.list' | 'memory.get' | 'provider.tools' | 'provider.call' | 'profile.list';
|
|
10
10
|
/** A directory operation. */
|
|
11
11
|
export type FakeDirectoryOp = 'directory.discovery' | 'directory.token' | 'directory.jwks';
|
|
12
12
|
/** What `intercept` / `failNext` / `gate` match: a runtime operation, `'*'` (every runtime operation), or a directory operation. */
|
package/dist/testing/cloud.js
CHANGED
|
@@ -28,6 +28,7 @@ const ROUTES = [
|
|
|
28
28
|
['GET', /^\/v1\/runs\/([^/]+)\/trace$/, 'trace.get'],
|
|
29
29
|
['GET', /^\/v1\/questions\/([^/]+)$/, 'question.get'],
|
|
30
30
|
['POST', /^\/v1\/questions\/([^/]+)\/answer$/, 'question.answer'],
|
|
31
|
+
['POST', /^\/v1\/questions\/([^/]+)\/cancel$/, 'question.cancel'],
|
|
31
32
|
['GET', /^\/v1\/memory$/, 'memory.list'],
|
|
32
33
|
['GET', /^\/v1\/memory\/document$/, 'memory.get'],
|
|
33
34
|
['GET', /^\/v1\/providers\/tools$/, 'provider.tools'],
|
|
@@ -606,7 +607,7 @@ export class FakeCloud {
|
|
|
606
607
|
}
|
|
607
608
|
const user = sub !== null ? this.users.get(sub) : undefined;
|
|
608
609
|
const runId = RUN_OPS.has(op) ? id : undefined;
|
|
609
|
-
const questionId = op === 'question.get' || op === 'question.answer' ? id : null;
|
|
610
|
+
const questionId = op === 'question.get' || op === 'question.answer' || op === 'question.cancel' ? id : null;
|
|
610
611
|
const questionRun = questionId === null ? undefined : user?.runs.find((run) => !run.deleted && run.questions.has(questionId));
|
|
611
612
|
const call = {
|
|
612
613
|
userId: user ? user.id : null, op, runId: runId ?? questionRun?.id ?? null, questionId, method: request.method, path: url.pathname,
|
|
@@ -776,6 +777,14 @@ export class FakeCloud {
|
|
|
776
777
|
run.answer(question, responses);
|
|
777
778
|
return { status: 200, body: { question_id: question, status: 'answered', answer: responses } };
|
|
778
779
|
}
|
|
780
|
+
case 'question.cancel': {
|
|
781
|
+
const { run, question } = this.question(user, id);
|
|
782
|
+
const current = run.questionObject(question);
|
|
783
|
+
if (current['status'] !== 'open')
|
|
784
|
+
throw refuse(409, 'run_settled', `question ${question} is already ${String(current['status'])}`, { details: { state: current['status'] } });
|
|
785
|
+
run.cancelQuestion(question);
|
|
786
|
+
return { status: 200, body: run.questionObject(question) };
|
|
787
|
+
}
|
|
779
788
|
case 'memory.list': return { status: 200, body: this.memoryList(user, query) };
|
|
780
789
|
case 'memory.get': {
|
|
781
790
|
this.memoryStore(query);
|
package/dist/testing/run.d.ts
CHANGED
|
@@ -55,9 +55,12 @@ export interface FakeQuestion {
|
|
|
55
55
|
slots?: RunQuestionSlotDTO[];
|
|
56
56
|
provider?: string;
|
|
57
57
|
tool?: string;
|
|
58
|
+
/** `notice` asks for nothing: it does not make the run `waiting_on_user`, emits no `waiting_on_user`, and is listed in `open_notice_ids`. Default `decision`. */
|
|
59
|
+
kind?: 'decision' | 'notice';
|
|
58
60
|
}
|
|
59
61
|
interface OpenQuestion {
|
|
60
62
|
question_id: string;
|
|
63
|
+
kind: 'decision' | 'notice';
|
|
61
64
|
title: string;
|
|
62
65
|
subtitle?: string;
|
|
63
66
|
steps: number;
|
|
@@ -112,6 +115,8 @@ export declare class FakeRun {
|
|
|
112
115
|
/** Messages not yet delivered to a turn (the start's `message` included). */
|
|
113
116
|
get pending(): FakeRunMessage[];
|
|
114
117
|
get pendingQuestionIds(): string[];
|
|
118
|
+
get openNoticeIds(): string[];
|
|
119
|
+
private openIds;
|
|
115
120
|
/** The run status, derived as the daemon's `runStatus` derives it. */
|
|
116
121
|
get status(): RunStatusDTO;
|
|
117
122
|
/** What the root is doing, derived from the event log as `runActivity` derives it with no open streaming block. */
|
|
@@ -156,6 +161,8 @@ export declare class FakeRun {
|
|
|
156
161
|
reopen(): void;
|
|
157
162
|
/** The app answered a question. */
|
|
158
163
|
answer(questionId: string, responses: Record<string, unknown>): void;
|
|
164
|
+
/** The app canceled a question: the person dismissed it. */
|
|
165
|
+
cancelQuestion(questionId: string): void;
|
|
159
166
|
questionObject(questionId: string): Record<string, unknown> | undefined;
|
|
160
167
|
/** Resolves when the run rests (`idle`, `waiting_on_user`, `settled`), is deleted, `seconds` pass, or `signal` aborts. */
|
|
161
168
|
waitResting(seconds: number, signal?: AbortSignal | null): Promise<void>;
|
package/dist/testing/run.js
CHANGED
|
@@ -74,7 +74,9 @@ export class FakeRun {
|
|
|
74
74
|
get lastTurnError() { return this.turnError; }
|
|
75
75
|
/** Messages not yet delivered to a turn (the start's `message` included). */
|
|
76
76
|
get pending() { return this.messages.filter((message) => !message.delivered); }
|
|
77
|
-
get pendingQuestionIds() { return
|
|
77
|
+
get pendingQuestionIds() { return this.openIds('decision'); }
|
|
78
|
+
get openNoticeIds() { return this.openIds('notice'); }
|
|
79
|
+
openIds(kind) { return [...this.questions.values()].filter((q) => q.status === 'open' && q.kind === kind).map((q) => q.question_id); }
|
|
78
80
|
/** The run status, derived as the daemon's `runStatus` derives it. */
|
|
79
81
|
get status() {
|
|
80
82
|
if (this.outcomeValue)
|
|
@@ -115,6 +117,7 @@ export class FakeRun {
|
|
|
115
117
|
...(this.outcomeValue && this.hasResult ? { result: this.resultValue } : {}),
|
|
116
118
|
// `last_report` is the newest report body (`runObject`); a scripted ending writes no report, so it is absent.
|
|
117
119
|
pending_question_ids: status === 'settled' ? [] : this.pendingQuestionIds,
|
|
120
|
+
open_notice_ids: status === 'settled' ? [] : this.openNoticeIds,
|
|
118
121
|
...(this.turnError ? { last_turn_error: this.turnError } : {}),
|
|
119
122
|
usage: usage(), payer: 'user', scopes: this.scopes, metadata: this.metadata, created_at: this.createdAt,
|
|
120
123
|
...(this.settledAt ? { settled_at: this.settledAt } : {}),
|
|
@@ -202,9 +205,14 @@ export class FakeRun {
|
|
|
202
205
|
ask(question) {
|
|
203
206
|
this.assertLive();
|
|
204
207
|
const id = `question-${this.id}-${this.questions.size + 1}`;
|
|
205
|
-
const
|
|
208
|
+
const kind = question.kind ?? 'decision';
|
|
209
|
+
const slots = question.slots ?? (kind === 'notice' ? [] : [{ id: 'answer', kind: 'text', step: 0, config: {} }]);
|
|
210
|
+
if (kind === 'notice' && slots.length > 0)
|
|
211
|
+
throw new Error('a notice takes no response: it has no slots');
|
|
206
212
|
const steps = question.steps ?? 1;
|
|
207
|
-
this.questions.set(id, { question_id: id, title: question.title, ...(question.subtitle === undefined ? {} : { subtitle: question.subtitle }), steps, slots, status: 'open' });
|
|
213
|
+
this.questions.set(id, { question_id: id, kind, title: question.title, ...(question.subtitle === undefined ? {} : { subtitle: question.subtitle }), steps, slots, status: 'open' });
|
|
214
|
+
if (kind === 'notice')
|
|
215
|
+
return id;
|
|
208
216
|
this.rested = true;
|
|
209
217
|
this.append('waiting_on_user', {
|
|
210
218
|
question_id: id, initiating_app: this.host.grantee, title: question.title,
|
|
@@ -256,13 +264,17 @@ export class FakeRun {
|
|
|
256
264
|
this.answered = true;
|
|
257
265
|
this.append('question.answered', { question_id: questionId, answer: responses });
|
|
258
266
|
}
|
|
267
|
+
/** The app canceled a question: the person dismissed it. */
|
|
268
|
+
cancelQuestion(questionId) {
|
|
269
|
+
this.questions.get(questionId).status = 'canceled';
|
|
270
|
+
}
|
|
259
271
|
questionObject(questionId) {
|
|
260
272
|
const q = this.questions.get(questionId);
|
|
261
273
|
if (!q)
|
|
262
274
|
return undefined;
|
|
263
275
|
return {
|
|
264
276
|
question_id: q.question_id, initiating_app: this.host.grantee, title: q.title, ...(q.subtitle === undefined ? {} : { subtitle: q.subtitle }),
|
|
265
|
-
steps: q.steps, slots: q.slots, run_id: this.id, node_id: this.id, status: q.status, ...(q.answer ? { answer: q.answer } : {}),
|
|
277
|
+
steps: q.steps, slots: q.slots, run_id: this.id, node_id: this.id, kind: q.kind, status: q.status, ...(q.answer ? { answer: q.answer } : {}),
|
|
266
278
|
};
|
|
267
279
|
}
|
|
268
280
|
/** Resolves when the run rests (`idle`, `waiting_on_user`, `settled`), is deleted, `seconds` pass, or `signal` aborts. */
|
package/dist/version.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** This SDK's release version. Informational only: compatibility is the contract level, not this number. */
|
|
2
|
-
export declare const SDK_VERSION = "0.3.
|
|
2
|
+
export declare const SDK_VERSION = "0.3.518";
|
|
3
3
|
/** The release sent in `Crouter-Minimum-Runtime-Version` on every request. It is frozen, so a runtime from before the
|
|
4
4
|
* contract level existed (which only knows this header) refuses this SDK, while every later runtime accepts it. */
|
|
5
5
|
export declare const LEGACY_FLOOR = "0.3.423";
|
package/dist/version.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// SDK_VERSION is written by scripts/release-version.mjs on every release; do not edit by hand.
|
|
2
2
|
/** This SDK's release version. Informational only: compatibility is the contract level, not this number. */
|
|
3
|
-
export const SDK_VERSION = '0.3.
|
|
3
|
+
export const SDK_VERSION = '0.3.518';
|
|
4
4
|
// LEGACY_FLOOR is written once by `scripts/contract.mjs release`, at the first release that carries the contract level
|
|
5
5
|
// (the `0.0.0` placeholder is replaced there and never again); do not edit by hand.
|
|
6
6
|
/** The release sent in `Crouter-Minimum-Runtime-Version` on every request. It is frozen, so a runtime from before the
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/sdk",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"crouterContract": "1.
|
|
3
|
+
"version": "0.3.518",
|
|
4
|
+
"crouterContract": "1.24",
|
|
5
5
|
"description": "Typed Node and browser client for running crouter agents through the crtrd /v1 API.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/index.js",
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"prepublishOnly": "node -e \"if (process.env.npm_config_tag !== 'next') { console.error('@north-light/sdk publishes only with --tag next; scripts/promote-sdk.mjs is the only mover of latest.'); process.exit(1); }\""
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@north-light/api": "0.3.
|
|
48
|
-
"@north-light/identity": "0.3.
|
|
47
|
+
"@north-light/api": "0.3.518",
|
|
48
|
+
"@north-light/identity": "0.3.518",
|
|
49
49
|
"jose": "^6.2.1"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|