@sjawhar/pi-legion-envoy 1.57.0 → 1.58.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/envoy.js +72 -29
- package/dist/legion.js +11 -4
- package/dist/skills/dispatch/SKILL.md +4 -2
- package/package.json +1 -1
package/dist/envoy.js
CHANGED
|
@@ -29649,6 +29649,13 @@ function date4(params) {
|
|
|
29649
29649
|
// ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/external.js
|
|
29650
29650
|
config(en_default());
|
|
29651
29651
|
// ../contracts/src/dispatch-api.ts
|
|
29652
|
+
function serviceSubjectLabel(subject) {
|
|
29653
|
+
const parts = subject.split(":");
|
|
29654
|
+
if (parts.length === 4 && parts[0] === "system" && parts[1] === "serviceaccount" && parts[2] !== "" && parts[3] !== "") {
|
|
29655
|
+
return `${parts[2]}/${parts[3]}`;
|
|
29656
|
+
}
|
|
29657
|
+
return subject;
|
|
29658
|
+
}
|
|
29652
29659
|
var ASK_TURNS = ["human", "agent"];
|
|
29653
29660
|
var DELIVERY_CAPABILITIES = ["aside", "btw", "steer"];
|
|
29654
29661
|
var DispatchEventSchema = object({
|
|
@@ -30052,7 +30059,7 @@ var dispatchToolSpecs = [
|
|
|
30052
30059
|
},
|
|
30053
30060
|
description: "Edit an open question in place. Use it to correct or refine the same decision; retract the " + "old ask and open a new one when the decision itself changes. Previous text remains in the " + "event log. Only the asking session can edit it; answered or resolved asks cannot be edited.",
|
|
30054
30061
|
arguments: (z) => ({
|
|
30055
|
-
ask: z.string().describe("Ask id
|
|
30062
|
+
ask: z.string().describe("Ask id (uuid); an 8+ hex prefix unique among this session's own open asks works too."),
|
|
30056
30063
|
question: z.string({ max: ASK_QUESTION_MAX }).describe(`Replacement decision question, at most ${ASK_QUESTION_MAX} characters.`).optional(),
|
|
30057
30064
|
options: z.array(z.object({
|
|
30058
30065
|
label: z.string().describe("Selectable option label."),
|
|
@@ -30078,7 +30085,7 @@ var dispatchToolSpecs = [
|
|
|
30078
30085
|
},
|
|
30079
30086
|
description: "Retract an open question that is moot or resolve one after finding the answer. This closes the question without answering it.",
|
|
30080
30087
|
arguments: (z) => ({
|
|
30081
|
-
ask: z.string().describe("Ask id to close."),
|
|
30088
|
+
ask: z.string().describe("Ask id (uuid) to close; an 8+ hex prefix unique among this session's own open asks " + "works too."),
|
|
30082
30089
|
kind: z.enum(["retracted", "resolved"]).describe("Whether the ask is retracted or self-resolved."),
|
|
30083
30090
|
reason: z.string({ min: 1 }).describe("Why the open ask no longer needs a human answer.")
|
|
30084
30091
|
})
|
|
@@ -30097,7 +30104,7 @@ var dispatchToolSpecs = [
|
|
|
30097
30104
|
example: { ask: "01234567-0000-4000-8000-000000000001", action: "follow" },
|
|
30098
30105
|
description: "Follow or unfollow an ask. Every session that opens or replies to an ask follows it: its answer, " + "edits, resolution, and replies reach that session directly. Unfollow to stop; follow to rejoin or " + "to hear an ask you never wrote to. Whole-issue subscription is separate: envoy_subscribe " + "notifications.dispatch.issue.<KEY>.>",
|
|
30099
30106
|
arguments: (z) => ({
|
|
30100
|
-
ask: z.string().describe("
|
|
30107
|
+
ask: z.string().describe("Ask id (uuid); an 8+ hex prefix unique among this session's own open asks works too."),
|
|
30101
30108
|
action: z.enum(["follow", "unfollow"]).describe("follow | unfollow")
|
|
30102
30109
|
}),
|
|
30103
30110
|
strict: true
|
|
@@ -32863,24 +32870,51 @@ function messageIdOf(value) {
|
|
|
32863
32870
|
return id === undefined ? undefined : normalizeUUID(id);
|
|
32864
32871
|
}
|
|
32865
32872
|
var idPrefixPattern = /^[0-9a-f][0-9a-f-]{7,}$/i;
|
|
32866
|
-
|
|
32867
|
-
|
|
32873
|
+
function refOwnerName(ref) {
|
|
32874
|
+
return ref.owner.kind === "issue" ? ref.owner.issue : `${ref.owner.project}/${ref.artifact}`;
|
|
32875
|
+
}
|
|
32876
|
+
async function resolveIdPrefix(tool, kind, id, ownerName, list) {
|
|
32877
|
+
const fullID = normalizeUUID(id);
|
|
32868
32878
|
if (fullID !== undefined)
|
|
32869
32879
|
return fullID;
|
|
32870
|
-
|
|
32871
|
-
if (!idPrefixPattern.test(ref.id)) {
|
|
32880
|
+
if (!idPrefixPattern.test(id)) {
|
|
32872
32881
|
throw new ToolInputError(tool, [
|
|
32873
|
-
`${kind} id ${
|
|
32882
|
+
`${kind} id ${id} must be a full uuid or a prefix of at least 8 hex characters`
|
|
32874
32883
|
]);
|
|
32875
32884
|
}
|
|
32876
|
-
const prefix =
|
|
32885
|
+
const prefix = id.toLowerCase();
|
|
32877
32886
|
const matches = (await list()).filter((item) => item.id.toLowerCase().startsWith(prefix));
|
|
32878
32887
|
if (matches.length === 1 && matches[0] !== undefined)
|
|
32879
32888
|
return matches[0].id;
|
|
32880
32889
|
throw new ToolInputError(tool, [
|
|
32881
|
-
matches.length === 0 ? `${kind} id ${
|
|
32890
|
+
matches.length === 0 ? `${kind} id ${id} matches none of the ${kind}s on ${ownerName}; use the full id` : `${kind} id ${id} matches ${matches.length} ${kind}s on ${ownerName}; use the full id`
|
|
32882
32891
|
]);
|
|
32883
32892
|
}
|
|
32893
|
+
var askIdShapeProblem = "ask ids are uuids (a prefix of at least 8 hex characters works)";
|
|
32894
|
+
async function sessionOpenAsks(client, sessionId) {
|
|
32895
|
+
const session = sessionId?.trim();
|
|
32896
|
+
if (!session)
|
|
32897
|
+
throw new Error("host session id is required to read this session's open asks");
|
|
32898
|
+
return (await client.openAsks(session)).asks;
|
|
32899
|
+
}
|
|
32900
|
+
function openAskHints(asks) {
|
|
32901
|
+
if (asks.length === 0)
|
|
32902
|
+
return "you have no open asks";
|
|
32903
|
+
const hints = asks.slice(0, askHintLimit).map((ask) => `${ask.id.slice(0, 8)} \u2014 ${textHead(ask.question)}`);
|
|
32904
|
+
return `your open asks: ${hints.join("; ")}`;
|
|
32905
|
+
}
|
|
32906
|
+
async function resolveAskArgument(tool, args, client, sessionId) {
|
|
32907
|
+
const id = askId(args);
|
|
32908
|
+
if (normalizeUUID(id) === undefined && !idPrefixPattern.test(id)) {
|
|
32909
|
+
const asks = await sessionOpenAsks(client, sessionId).catch(() => {
|
|
32910
|
+
return;
|
|
32911
|
+
});
|
|
32912
|
+
throw new ToolInputError(tool, [
|
|
32913
|
+
asks === undefined ? askIdShapeProblem : `${askIdShapeProblem}; ${openAskHints(asks)}`
|
|
32914
|
+
]);
|
|
32915
|
+
}
|
|
32916
|
+
return resolveIdPrefix(tool, "ask", id, "this session", () => sessionOpenAsks(client, sessionId));
|
|
32917
|
+
}
|
|
32884
32918
|
function messageInReplyTo(args) {
|
|
32885
32919
|
const inReplyTo = optionalString(args, "in_reply_to");
|
|
32886
32920
|
return inReplyTo === undefined ? undefined : messageIdOf(inReplyTo);
|
|
@@ -32899,7 +32933,8 @@ function argumentProblems(tool, args) {
|
|
|
32899
32933
|
break;
|
|
32900
32934
|
}
|
|
32901
32935
|
case "dispatch_edit_ask":
|
|
32902
|
-
case "dispatch_resolve_ask":
|
|
32936
|
+
case "dispatch_resolve_ask":
|
|
32937
|
+
case "dispatch_follow": {
|
|
32903
32938
|
const ask = optionalString(args, "ask");
|
|
32904
32939
|
if (ask?.startsWith("dispatch://") && parseDispatchRef(ask)?.kind !== "ask") {
|
|
32905
32940
|
problems.push(askIdProblem);
|
|
@@ -33292,9 +33327,16 @@ function eventHead(event) {
|
|
|
33292
33327
|
return;
|
|
33293
33328
|
}
|
|
33294
33329
|
}
|
|
33330
|
+
function actorText(actor) {
|
|
33331
|
+
const service = actor.kind === "session" ? actor.service : undefined;
|
|
33332
|
+
if (service === undefined) {
|
|
33333
|
+
return `${actor.kind} ${actor.id}`;
|
|
33334
|
+
}
|
|
33335
|
+
return `${actor.kind} ${actor.id} (as ${serviceSubjectLabel(service)})`;
|
|
33336
|
+
}
|
|
33295
33337
|
function eventLine(event) {
|
|
33296
33338
|
const head = eventHead(event);
|
|
33297
|
-
return `- #${event.seq} ${event.type} \xB7 ${event.actor
|
|
33339
|
+
return `- #${event.seq} ${event.type} \xB7 ${actorText(event.actor)} \xB7 ${event.created_at}${head === undefined || head === "" ? "" : ` \xB7 ${head}`}`;
|
|
33298
33340
|
}
|
|
33299
33341
|
function childrenSummary(issue) {
|
|
33300
33342
|
return [
|
|
@@ -33307,7 +33349,7 @@ function childrenSummary(issue) {
|
|
|
33307
33349
|
function askSummary({ ask, replies }, graph) {
|
|
33308
33350
|
const answer = ask.answer;
|
|
33309
33351
|
const chain = replies.flatMap((reply) => [
|
|
33310
|
-
`${reply.id} \xB7 ${reply.author
|
|
33352
|
+
`${reply.id} \xB7 ${actorText(reply.author)}`,
|
|
33311
33353
|
`Body: ${reply.body}`
|
|
33312
33354
|
]);
|
|
33313
33355
|
return [
|
|
@@ -33374,12 +33416,12 @@ function formatOpenAsksSummary(response, baseUrl) {
|
|
|
33374
33416
|
}
|
|
33375
33417
|
function commentSummary({ comment, replies }, graph) {
|
|
33376
33418
|
const root = [
|
|
33377
|
-
`${comment.id} \xB7 ${comment.author
|
|
33419
|
+
`${comment.id} \xB7 ${actorText(comment.author)}`,
|
|
33378
33420
|
...comment.anchor?.quote === undefined ? [] : [`> ${comment.anchor.quote}`],
|
|
33379
33421
|
`Body: ${comment.body}`
|
|
33380
33422
|
];
|
|
33381
33423
|
const chain = replies.flatMap((reply) => [
|
|
33382
|
-
`${reply.id} \xB7 ${reply.author
|
|
33424
|
+
`${reply.id} \xB7 ${actorText(reply.author)}`,
|
|
33383
33425
|
...reply.anchor?.quote === undefined ? [] : [`> ${reply.anchor.quote}`],
|
|
33384
33426
|
`Body: ${reply.body}`
|
|
33385
33427
|
]);
|
|
@@ -33393,12 +33435,9 @@ function commentSummary({ comment, replies }, graph) {
|
|
|
33393
33435
|
`);
|
|
33394
33436
|
}
|
|
33395
33437
|
function messageSummary({ message, replies }, graph) {
|
|
33396
|
-
const root = [
|
|
33397
|
-
`${message.id} \xB7 ${message.author.kind} ${message.author.id}`,
|
|
33398
|
-
`Body: ${message.body}`
|
|
33399
|
-
];
|
|
33438
|
+
const root = [`${message.id} \xB7 ${actorText(message.author)}`, `Body: ${message.body}`];
|
|
33400
33439
|
const chain = replies.flatMap((reply) => [
|
|
33401
|
-
`${reply.id} \xB7 ${reply.author
|
|
33440
|
+
`${reply.id} \xB7 ${actorText(reply.author)}`,
|
|
33402
33441
|
`Body: ${reply.body}`
|
|
33403
33442
|
]);
|
|
33404
33443
|
return [
|
|
@@ -33480,9 +33519,11 @@ async function executeDispatchTool(input) {
|
|
|
33480
33519
|
const client = dispatchClient();
|
|
33481
33520
|
const identity = await client.whoami();
|
|
33482
33521
|
const owner = identity.kind === "agent" ? identity.owner : identity.login.toLowerCase();
|
|
33522
|
+
const service = identity.kind === "agent" ? identity.service ?? null : null;
|
|
33523
|
+
const unowned = "no owner, so issues you create without an assignee are unassigned (or inherit their parent's).";
|
|
33483
33524
|
return {
|
|
33484
|
-
text: owner
|
|
33485
|
-
details: { session: sessionId, owner }
|
|
33525
|
+
text: owner !== null ? `Session ${sessionId} acts for ${owner}: issues you create without an assignee are assigned to ${owner}.` : service !== null ? `Session ${sessionId} runs as service ${serviceSubjectLabel(service)}: ${unowned}` : `Session ${sessionId} runs under the shared token: ${unowned}`,
|
|
33526
|
+
details: { session: sessionId, owner, service }
|
|
33486
33527
|
};
|
|
33487
33528
|
}
|
|
33488
33529
|
const args = parsed.success ? parsed.data : ownerArguments.args;
|
|
@@ -33693,7 +33734,8 @@ async function executeDispatchTool(input) {
|
|
|
33693
33734
|
}
|
|
33694
33735
|
case "dispatch_resolve_ask": {
|
|
33695
33736
|
const kind = stringArg(args, "kind");
|
|
33696
|
-
const
|
|
33737
|
+
const id = await resolveAskArgument(input.tool, args, client, input.sessionId);
|
|
33738
|
+
const ask = await client.resolveAsk(id, {
|
|
33697
33739
|
kind,
|
|
33698
33740
|
reason: stringArg(args, "reason"),
|
|
33699
33741
|
actor
|
|
@@ -33712,11 +33754,11 @@ async function executeDispatchTool(input) {
|
|
|
33712
33754
|
let document;
|
|
33713
33755
|
if (ref?.owner.kind === "issue") {
|
|
33714
33756
|
const issueKey = ref.owner.issue;
|
|
33715
|
-
id = await resolveIdPrefix(input.tool, "comment", ref, () => client.getComments(issueKey));
|
|
33757
|
+
id = await resolveIdPrefix(input.tool, "comment", ref.id, refOwnerName(ref), () => client.getComments(issueKey));
|
|
33716
33758
|
} else if (ref !== null) {
|
|
33717
33759
|
const artifact = (await resolveArtifact(client, ref.owner, ref.artifact)).artifact;
|
|
33718
33760
|
document = artifact;
|
|
33719
|
-
id = await resolveIdPrefix(input.tool, "comment", ref, () => client.getArtifactComments(artifact.id));
|
|
33761
|
+
id = await resolveIdPrefix(input.tool, "comment", ref.id, refOwnerName(ref), () => client.getArtifactComments(artifact.id));
|
|
33720
33762
|
}
|
|
33721
33763
|
const comment = await client.resolveComment(id, actor);
|
|
33722
33764
|
const { label, details } = await commentOwnerResult(client, comment, document);
|
|
@@ -33760,7 +33802,8 @@ ${followsAsk(askOwner)}`,
|
|
|
33760
33802
|
const options = args.options;
|
|
33761
33803
|
const multiple = optionalBoolean(args, "multiple");
|
|
33762
33804
|
const urgency = askUrgency(args);
|
|
33763
|
-
const
|
|
33805
|
+
const id = await resolveAskArgument(input.tool, args, client, input.sessionId);
|
|
33806
|
+
const ask = await client.editAsk(id, {
|
|
33764
33807
|
...question === undefined ? {} : { question },
|
|
33765
33808
|
...Array.isArray(options) ? { options } : {},
|
|
33766
33809
|
...multiple === undefined ? {} : { multiple },
|
|
@@ -33987,7 +34030,7 @@ ${trailer.join(`
|
|
|
33987
34030
|
const sessionId = input.sessionId?.trim();
|
|
33988
34031
|
if (!sessionId)
|
|
33989
34032
|
throw new Error("host session id is required for dispatch_follow");
|
|
33990
|
-
const ask =
|
|
34033
|
+
const ask = await resolveAskArgument(input.tool, args, client, sessionId);
|
|
33991
34034
|
const action = stringArg(args, "action");
|
|
33992
34035
|
if (action === "unfollow") {
|
|
33993
34036
|
await client.unfollowAsk(ask, sessionId, actor);
|
|
@@ -34003,7 +34046,7 @@ ${trailer.join(`
|
|
|
34003
34046
|
case "dispatch_read": {
|
|
34004
34047
|
if (ownerArguments.ref?.kind === "ask") {
|
|
34005
34048
|
const ref = ownerArguments.ref;
|
|
34006
|
-
const id = await resolveIdPrefix(input.tool, "ask", ref, async () => ref.owner.kind === "issue" ? client.listIssueAsks(ref.owner.issue) : client.getArtifactAsks((await resolveArtifact(client, ref.owner, ref.artifact)).artifact.id, "all"));
|
|
34049
|
+
const id = await resolveIdPrefix(input.tool, "ask", ref.id, refOwnerName(ref), async () => ref.owner.kind === "issue" ? client.listIssueAsks(ref.owner.issue) : client.getArtifactAsks((await resolveArtifact(client, ref.owner, ref.artifact)).artifact.id, "all"));
|
|
34007
34050
|
const askRead = await client.getAsk(id);
|
|
34008
34051
|
const askRef = refTarget(ref, "ask", id);
|
|
34009
34052
|
return {
|
|
@@ -34013,7 +34056,7 @@ ${trailer.join(`
|
|
|
34013
34056
|
}
|
|
34014
34057
|
if (ownerArguments.ref?.kind === "comment") {
|
|
34015
34058
|
const ref = ownerArguments.ref;
|
|
34016
|
-
const id = await resolveIdPrefix(input.tool, "comment", ref, async () => ref.owner.kind === "issue" ? client.getComments(ref.owner.issue) : client.getArtifactComments((await resolveArtifact(client, ref.owner, ref.artifact)).artifact.id));
|
|
34059
|
+
const id = await resolveIdPrefix(input.tool, "comment", ref.id, refOwnerName(ref), async () => ref.owner.kind === "issue" ? client.getComments(ref.owner.issue) : client.getArtifactComments((await resolveArtifact(client, ref.owner, ref.artifact)).artifact.id));
|
|
34017
34060
|
const comment = await client.getComment(id);
|
|
34018
34061
|
const commentRef = refTarget(ref, "comment", id);
|
|
34019
34062
|
return {
|
package/dist/legion.js
CHANGED
|
@@ -16145,7 +16145,7 @@ import { logger } from "@oh-my-pi/pi-utils";
|
|
|
16145
16145
|
// package.json
|
|
16146
16146
|
var package_default = {
|
|
16147
16147
|
name: "@sjawhar/pi-legion-envoy",
|
|
16148
|
-
version: "1.
|
|
16148
|
+
version: "1.58.0",
|
|
16149
16149
|
type: "module",
|
|
16150
16150
|
omp: {
|
|
16151
16151
|
extensions: [
|
|
@@ -28968,6 +28968,13 @@ function preprocess(fn, schema) {
|
|
|
28968
28968
|
return pipe(transform(fn), schema);
|
|
28969
28969
|
}
|
|
28970
28970
|
// ../contracts/src/dispatch-api.ts
|
|
28971
|
+
function serviceSubjectLabel(subject) {
|
|
28972
|
+
const parts = subject.split(":");
|
|
28973
|
+
if (parts.length === 4 && parts[0] === "system" && parts[1] === "serviceaccount" && parts[2] !== "" && parts[3] !== "") {
|
|
28974
|
+
return `${parts[2]}/${parts[3]}`;
|
|
28975
|
+
}
|
|
28976
|
+
return subject;
|
|
28977
|
+
}
|
|
28971
28978
|
var ASK_TURNS = ["human", "agent"];
|
|
28972
28979
|
var DELIVERY_CAPABILITIES = ["aside", "btw", "steer"];
|
|
28973
28980
|
var DispatchEventSchema = object({
|
|
@@ -29371,7 +29378,7 @@ var dispatchToolSpecs = [
|
|
|
29371
29378
|
},
|
|
29372
29379
|
description: "Edit an open question in place. Use it to correct or refine the same decision; retract the " + "old ask and open a new one when the decision itself changes. Previous text remains in the " + "event log. Only the asking session can edit it; answered or resolved asks cannot be edited.",
|
|
29373
29380
|
arguments: (z) => ({
|
|
29374
|
-
ask: z.string().describe("Ask id
|
|
29381
|
+
ask: z.string().describe("Ask id (uuid); an 8+ hex prefix unique among this session's own open asks works too."),
|
|
29375
29382
|
question: z.string({ max: ASK_QUESTION_MAX }).describe(`Replacement decision question, at most ${ASK_QUESTION_MAX} characters.`).optional(),
|
|
29376
29383
|
options: z.array(z.object({
|
|
29377
29384
|
label: z.string().describe("Selectable option label."),
|
|
@@ -29397,7 +29404,7 @@ var dispatchToolSpecs = [
|
|
|
29397
29404
|
},
|
|
29398
29405
|
description: "Retract an open question that is moot or resolve one after finding the answer. This closes the question without answering it.",
|
|
29399
29406
|
arguments: (z) => ({
|
|
29400
|
-
ask: z.string().describe("Ask id to close."),
|
|
29407
|
+
ask: z.string().describe("Ask id (uuid) to close; an 8+ hex prefix unique among this session's own open asks " + "works too."),
|
|
29401
29408
|
kind: z.enum(["retracted", "resolved"]).describe("Whether the ask is retracted or self-resolved."),
|
|
29402
29409
|
reason: z.string({ min: 1 }).describe("Why the open ask no longer needs a human answer.")
|
|
29403
29410
|
})
|
|
@@ -29416,7 +29423,7 @@ var dispatchToolSpecs = [
|
|
|
29416
29423
|
example: { ask: "01234567-0000-4000-8000-000000000001", action: "follow" },
|
|
29417
29424
|
description: "Follow or unfollow an ask. Every session that opens or replies to an ask follows it: its answer, " + "edits, resolution, and replies reach that session directly. Unfollow to stop; follow to rejoin or " + "to hear an ask you never wrote to. Whole-issue subscription is separate: envoy_subscribe " + "notifications.dispatch.issue.<KEY>.>",
|
|
29418
29425
|
arguments: (z) => ({
|
|
29419
|
-
ask: z.string().describe("
|
|
29426
|
+
ask: z.string().describe("Ask id (uuid); an 8+ hex prefix unique among this session's own open asks works too."),
|
|
29420
29427
|
action: z.enum(["follow", "unfollow"]).describe("follow | unfollow")
|
|
29421
29428
|
}),
|
|
29422
29429
|
strict: true
|
|
@@ -148,7 +148,7 @@ An ask goes to the issue's assignee: their Inbox opens on **Mine**, which lists
|
|
|
148
148
|
```ts
|
|
149
149
|
dispatch_whoami({})
|
|
150
150
|
```
|
|
151
|
-
It returns `details` `{ session, owner }`: `owner` is the lowercase login of the human whose personal token you run under, or `null` under the shared token. An issue you create without `assignee` goes to your owner;
|
|
151
|
+
It returns `details` `{ session, owner, service }`: `owner` is the lowercase login of the human whose personal token you run under, or `null` under the shared token or a verified service token, in which case `service` is that token's subject (`system:serviceaccount:<namespace>:<name>`, `null` otherwise) and every write you make is rendered `(as <namespace>/<name>)` — the namespace is kept because every namespace has a `default` service account, and a subject that is not a Kubernetes one is shown whole. An issue you create without `assignee` goes to your owner; with no owner it inherits its parent's assignee, or stays unassigned without a parent. If an issue you are asking on is unassigned and the answer matters, assign it to your owner (`PATCH /api/v1/issues/{key}` with `{"assignee": "<login>"}`; any authenticated caller may reassign, and an unlisted login is refused with `ASSIGNEE_NOT_ALLOWED`) or name in the question who should answer it. Never reassign an issue a human holds to get an answer faster: that is the human's call.
|
|
152
152
|
|
|
153
153
|
Architects create newly tracked child work with:
|
|
154
154
|
```ts
|
|
@@ -708,7 +708,9 @@ the host tells you once per ask. Leave a thread you no longer need, or rejoin on
|
|
|
708
708
|
dispatch_follow({ ask, action: "follow" | "unfollow" })
|
|
709
709
|
```
|
|
710
710
|
|
|
711
|
-
`ask` is the full ask id or a `dispatch://KEY/ask/<id>` reference
|
|
711
|
+
`ask` is the full ask id or a `dispatch://KEY/ask/<id>` reference; `dispatch_follow`, `dispatch_edit_ask`, and
|
|
712
|
+
`dispatch_resolve_ask` also take an 8+ hex prefix that is unique among your own open asks, and refuse anything shorter by naming
|
|
713
|
+
those asks. A human may also remove you from the ask card; either way you are
|
|
712
714
|
told with an `ask.follower_removed` notice, and a human adding you arrives as `ask.follower_added`.
|
|
713
715
|
|
|
714
716
|
No write subscribes you to an issue or document. Following covers your own asks and the threads you joined; everything else on the
|